##// END OF EJS Templates
pylons: remove pylons as dependency...
marcink -
r2351:59272121 default
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -1,276 +1,255 b''
1 # Overrides for the generated python-packages.nix
1 # Overrides for the generated python-packages.nix
2 #
2 #
3 # This function is intended to be used as an extension to the generated file
3 # This function is intended to be used as an extension to the generated file
4 # python-packages.nix. The main objective is to add needed dependencies of C
4 # python-packages.nix. The main objective is to add needed dependencies of C
5 # libraries and tweak the build instructions where needed.
5 # libraries and tweak the build instructions where needed.
6
6
7 { pkgs, basePythonPackages }:
7 { pkgs, basePythonPackages }:
8
8
9 let
9 let
10 sed = "sed -i";
10 sed = "sed -i";
11 localLicenses = {
11 localLicenses = {
12 repoze = {
12 repoze = {
13 fullName = "Repoze License";
13 fullName = "Repoze License";
14 url = http://www.repoze.org/LICENSE.txt;
14 url = http://www.repoze.org/LICENSE.txt;
15 };
15 };
16 };
16 };
17
17
18 in
18 in
19
19
20 self: super: {
20 self: super: {
21
21
22 appenlight-client = super.appenlight-client.override (attrs: {
22 appenlight-client = super.appenlight-client.override (attrs: {
23 meta = {
23 meta = {
24 license = [ pkgs.lib.licenses.bsdOriginal ];
24 license = [ pkgs.lib.licenses.bsdOriginal ];
25 };
25 };
26 });
26 });
27
27
28 future = super.future.override (attrs: {
28 future = super.future.override (attrs: {
29 meta = {
29 meta = {
30 license = [ pkgs.lib.licenses.mit ];
30 license = [ pkgs.lib.licenses.mit ];
31 };
31 };
32 });
32 });
33
33
34 gnureadline = super.gnureadline.override (attrs: {
34 gnureadline = super.gnureadline.override (attrs: {
35 buildInputs = attrs.buildInputs ++ [
35 buildInputs = attrs.buildInputs ++ [
36 pkgs.ncurses
36 pkgs.ncurses
37 ];
37 ];
38 patchPhase = ''
38 patchPhase = ''
39 substituteInPlace setup.py --replace "/bin/bash" "${pkgs.bash}/bin/bash"
39 substituteInPlace setup.py --replace "/bin/bash" "${pkgs.bash}/bin/bash"
40 '';
40 '';
41 });
41 });
42
42
43 gunicorn = super.gunicorn.override (attrs: {
43 gunicorn = super.gunicorn.override (attrs: {
44 propagatedBuildInputs = attrs.propagatedBuildInputs ++ [
44 propagatedBuildInputs = attrs.propagatedBuildInputs ++ [
45 # johbo: futures is needed as long as we are on Python 2, otherwise
45 # johbo: futures is needed as long as we are on Python 2, otherwise
46 # gunicorn explodes if used with multiple threads per worker.
46 # gunicorn explodes if used with multiple threads per worker.
47 self.futures
47 self.futures
48 ];
48 ];
49 });
49 });
50
50
51 nbconvert = super.nbconvert.override (attrs: {
51 nbconvert = super.nbconvert.override (attrs: {
52 propagatedBuildInputs = attrs.propagatedBuildInputs ++ [
52 propagatedBuildInputs = attrs.propagatedBuildInputs ++ [
53 # marcink: plug in jupyter-client for notebook rendering
53 # marcink: plug in jupyter-client for notebook rendering
54 self.jupyter-client
54 self.jupyter-client
55 ];
55 ];
56 });
56 });
57
57
58 ipython = super.ipython.override (attrs: {
58 ipython = super.ipython.override (attrs: {
59 propagatedBuildInputs = attrs.propagatedBuildInputs ++ [
59 propagatedBuildInputs = attrs.propagatedBuildInputs ++ [
60 self.gnureadline
60 self.gnureadline
61 ];
61 ];
62 });
62 });
63
63
64 celery = super.celery.override (attrs: {
65 # The current version of kombu needs some patching to work with the
66 # other libs. Should be removed once we update celery and kombu.
67 patches = [
68 ./patch-celery-dateutil.diff
69 ];
70 });
71
72 kombu = super.kombu.override (attrs: {
73 # The current version of kombu needs some patching to work with the
74 # other libs. Should be removed once we update celery and kombu.
75 patches = [
76 ./patch-kombu-py-2-7-11.diff
77 ./patch-kombu-msgpack.diff
78 ];
79 });
80
81 lxml = super.lxml.override (attrs: {
64 lxml = super.lxml.override (attrs: {
82 # johbo: On 16.09 we need this to compile on darwin, otherwise compilation
65 # johbo: On 16.09 we need this to compile on darwin, otherwise compilation
83 # fails on Darwin.
66 # fails on Darwin.
84 hardeningDisable = if pkgs.stdenv.isDarwin then [ "format" ] else null;
67 hardeningDisable = if pkgs.stdenv.isDarwin then [ "format" ] else null;
85 buildInputs = with self; [
68 buildInputs = with self; [
86 pkgs.libxml2
69 pkgs.libxml2
87 pkgs.libxslt
70 pkgs.libxslt
88 ];
71 ];
89 });
72 });
90
73
91 MySQL-python = super.MySQL-python.override (attrs: {
74 MySQL-python = super.MySQL-python.override (attrs: {
92 buildInputs = attrs.buildInputs ++ [
75 buildInputs = attrs.buildInputs ++ [
93 pkgs.openssl
76 pkgs.openssl
94 ];
77 ];
95 propagatedBuildInputs = attrs.propagatedBuildInputs ++ [
78 propagatedBuildInputs = attrs.propagatedBuildInputs ++ [
96 pkgs.mysql.lib
79 pkgs.mysql.lib
97 pkgs.zlib
80 pkgs.zlib
98 ];
81 ];
99 });
82 });
100
83
101 psutil = super.psutil.override (attrs: {
84 psutil = super.psutil.override (attrs: {
102 buildInputs = attrs.buildInputs ++
85 buildInputs = attrs.buildInputs ++
103 pkgs.lib.optional pkgs.stdenv.isDarwin pkgs.darwin.IOKit;
86 pkgs.lib.optional pkgs.stdenv.isDarwin pkgs.darwin.IOKit;
104 });
87 });
105
88
106 psycopg2 = super.psycopg2.override (attrs: {
89 psycopg2 = super.psycopg2.override (attrs: {
107 buildInputs = attrs.buildInputs ++
90 buildInputs = attrs.buildInputs ++
108 pkgs.lib.optional pkgs.stdenv.isDarwin pkgs.openssl;
91 pkgs.lib.optional pkgs.stdenv.isDarwin pkgs.openssl;
109 propagatedBuildInputs = attrs.propagatedBuildInputs ++ [
92 propagatedBuildInputs = attrs.propagatedBuildInputs ++ [
110 pkgs.postgresql
93 pkgs.postgresql
111 ];
94 ];
112 meta = {
95 meta = {
113 license = pkgs.lib.licenses.lgpl3Plus;
96 license = pkgs.lib.licenses.lgpl3Plus;
114 };
97 };
115 });
98 });
116
99
117 py-gfm = super.py-gfm.override {
100 py-gfm = super.py-gfm.override {
118 name = "py-gfm-0.1.3.rhodecode-upstream1";
101 name = "py-gfm-0.1.3.rhodecode-upstream1";
119 };
102 };
120
103
121 pycurl = super.pycurl.override (attrs: {
104 pycurl = super.pycurl.override (attrs: {
122 propagatedBuildInputs = attrs.propagatedBuildInputs ++ [
105 propagatedBuildInputs = attrs.propagatedBuildInputs ++ [
123 pkgs.curl
106 pkgs.curl
124 pkgs.openssl
107 pkgs.openssl
125 ];
108 ];
126 preConfigure = ''
109 preConfigure = ''
127 substituteInPlace setup.py --replace '--static-libs' '--libs'
110 substituteInPlace setup.py --replace '--static-libs' '--libs'
128 export PYCURL_SSL_LIBRARY=openssl
111 export PYCURL_SSL_LIBRARY=openssl
129 '';
112 '';
130 meta = {
113 meta = {
131 # TODO: It is LGPL and MIT
114 # TODO: It is LGPL and MIT
132 license = pkgs.lib.licenses.mit;
115 license = pkgs.lib.licenses.mit;
133 };
116 };
134 });
117 });
135
118
136 Pylons = super.Pylons.override (attrs: {
137 name = "Pylons-1.0.2.rhodecode-patch1";
138 });
139
140 pyramid = super.pyramid.override (attrs: {
119 pyramid = super.pyramid.override (attrs: {
141 postFixup = ''
120 postFixup = ''
142 wrapPythonPrograms
121 wrapPythonPrograms
143 # TODO: johbo: "wrapPython" adds this magic line which
122 # TODO: johbo: "wrapPython" adds this magic line which
144 # confuses pserve.
123 # confuses pserve.
145 ${sed} '/import sys; sys.argv/d' $out/bin/.pserve-wrapped
124 ${sed} '/import sys; sys.argv/d' $out/bin/.pserve-wrapped
146 '';
125 '';
147 meta = {
126 meta = {
148 license = localLicenses.repoze;
127 license = localLicenses.repoze;
149 };
128 };
150 });
129 });
151
130
152 pyramid-debugtoolbar = super.pyramid-debugtoolbar.override (attrs: {
131 pyramid-debugtoolbar = super.pyramid-debugtoolbar.override (attrs: {
153 meta = {
132 meta = {
154 license = [ pkgs.lib.licenses.bsdOriginal localLicenses.repoze ];
133 license = [ pkgs.lib.licenses.bsdOriginal localLicenses.repoze ];
155 };
134 };
156 });
135 });
157
136
158 pysqlite = super.pysqlite.override (attrs: {
137 pysqlite = super.pysqlite.override (attrs: {
159 propagatedBuildInputs = [
138 propagatedBuildInputs = [
160 pkgs.sqlite
139 pkgs.sqlite
161 ];
140 ];
162 meta = {
141 meta = {
163 license = [ pkgs.lib.licenses.zlib pkgs.lib.licenses.libpng ];
142 license = [ pkgs.lib.licenses.zlib pkgs.lib.licenses.libpng ];
164 };
143 };
165 });
144 });
166
145
167 pytest-runner = super.pytest-runner.override (attrs: {
146 pytest-runner = super.pytest-runner.override (attrs: {
168 propagatedBuildInputs = [
147 propagatedBuildInputs = [
169 self.setuptools-scm
148 self.setuptools-scm
170 ];
149 ];
171 });
150 });
172
151
173 python-ldap = super.python-ldap.override (attrs: {
152 python-ldap = super.python-ldap.override (attrs: {
174 propagatedBuildInputs = attrs.propagatedBuildInputs ++ [
153 propagatedBuildInputs = attrs.propagatedBuildInputs ++ [
175 pkgs.cyrus_sasl
154 pkgs.cyrus_sasl
176 pkgs.openldap
155 pkgs.openldap
177 pkgs.openssl
156 pkgs.openssl
178 ];
157 ];
179 # TODO: johbo: Remove the "or" once we drop 16.03 support.
158 # TODO: johbo: Remove the "or" once we drop 16.03 support.
180 NIX_CFLAGS_COMPILE = "-I${pkgs.cyrus_sasl.dev or pkgs.cyrus_sasl}/include/sasl";
159 NIX_CFLAGS_COMPILE = "-I${pkgs.cyrus_sasl.dev or pkgs.cyrus_sasl}/include/sasl";
181 });
160 });
182
161
183 python-pam = super.python-pam.override (attrs:
162 python-pam = super.python-pam.override (attrs:
184 let
163 let
185 includeLibPam = pkgs.stdenv.isLinux;
164 includeLibPam = pkgs.stdenv.isLinux;
186 in {
165 in {
187 # TODO: johbo: Move the option up into the default.nix, we should
166 # TODO: johbo: Move the option up into the default.nix, we should
188 # include python-pam only on supported platforms.
167 # include python-pam only on supported platforms.
189 propagatedBuildInputs = attrs.propagatedBuildInputs ++
168 propagatedBuildInputs = attrs.propagatedBuildInputs ++
190 pkgs.lib.optional includeLibPam [
169 pkgs.lib.optional includeLibPam [
191 pkgs.pam
170 pkgs.pam
192 ];
171 ];
193 # TODO: johbo: Check if this can be avoided, or transform into
172 # TODO: johbo: Check if this can be avoided, or transform into
194 # a real patch
173 # a real patch
195 patchPhase = pkgs.lib.optionals includeLibPam ''
174 patchPhase = pkgs.lib.optionals includeLibPam ''
196 substituteInPlace pam.py \
175 substituteInPlace pam.py \
197 --replace 'find_library("pam")' '"${pkgs.pam}/lib/libpam.so.0"'
176 --replace 'find_library("pam")' '"${pkgs.pam}/lib/libpam.so.0"'
198 '';
177 '';
199 });
178 });
200
179
201 URLObject = super.URLObject.override (attrs: {
180 URLObject = super.URLObject.override (attrs: {
202 meta = {
181 meta = {
203 license = {
182 license = {
204 spdxId = "Unlicense";
183 spdxId = "Unlicense";
205 fullName = "The Unlicense";
184 fullName = "The Unlicense";
206 url = http://unlicense.org/;
185 url = http://unlicense.org/;
207 };
186 };
208 };
187 };
209 });
188 });
210
189
211 amqplib = super.amqplib.override (attrs: {
190 amqplib = super.amqplib.override (attrs: {
212 meta = {
191 meta = {
213 license = pkgs.lib.licenses.lgpl3;
192 license = pkgs.lib.licenses.lgpl3;
214 };
193 };
215 });
194 });
216
195
217 docutils = super.docutils.override (attrs: {
196 docutils = super.docutils.override (attrs: {
218 meta = {
197 meta = {
219 license = pkgs.lib.licenses.bsd2;
198 license = pkgs.lib.licenses.bsd2;
220 };
199 };
221 });
200 });
222
201
223 colander = super.colander.override (attrs: {
202 colander = super.colander.override (attrs: {
224 meta = {
203 meta = {
225 license = localLicenses.repoze;
204 license = localLicenses.repoze;
226 };
205 };
227 });
206 });
228
207
229 pyramid-beaker = super.pyramid-beaker.override (attrs: {
208 pyramid-beaker = super.pyramid-beaker.override (attrs: {
230 meta = {
209 meta = {
231 license = localLicenses.repoze;
210 license = localLicenses.repoze;
232 };
211 };
233 });
212 });
234
213
235 pyramid-mako = super.pyramid-mako.override (attrs: {
214 pyramid-mako = super.pyramid-mako.override (attrs: {
236 meta = {
215 meta = {
237 license = localLicenses.repoze;
216 license = localLicenses.repoze;
238 };
217 };
239 });
218 });
240
219
241 repoze.lru = super.repoze.lru.override (attrs: {
220 repoze.lru = super.repoze.lru.override (attrs: {
242 meta = {
221 meta = {
243 license = localLicenses.repoze;
222 license = localLicenses.repoze;
244 };
223 };
245 });
224 });
246
225
247 recaptcha-client = super.recaptcha-client.override (attrs: {
226 recaptcha-client = super.recaptcha-client.override (attrs: {
248 meta = {
227 meta = {
249 # TODO: It is MIT/X11
228 # TODO: It is MIT/X11
250 license = pkgs.lib.licenses.mit;
229 license = pkgs.lib.licenses.mit;
251 };
230 };
252 });
231 });
253
232
254 python-editor = super.python-editor.override (attrs: {
233 python-editor = super.python-editor.override (attrs: {
255 meta = {
234 meta = {
256 license = pkgs.lib.licenses.asl20;
235 license = pkgs.lib.licenses.asl20;
257 };
236 };
258 });
237 });
259
238
260 translationstring = super.translationstring.override (attrs: {
239 translationstring = super.translationstring.override (attrs: {
261 meta = {
240 meta = {
262 license = localLicenses.repoze;
241 license = localLicenses.repoze;
263 };
242 };
264 });
243 });
265
244
266 venusian = super.venusian.override (attrs: {
245 venusian = super.venusian.override (attrs: {
267 meta = {
246 meta = {
268 license = localLicenses.repoze;
247 license = localLicenses.repoze;
269 };
248 };
270 });
249 });
271
250
272 # Avoid that setuptools is replaced, this leads to trouble
251 # Avoid that setuptools is replaced, this leads to trouble
273 # with buildPythonPackage.
252 # with buildPythonPackage.
274 setuptools = basePythonPackages.setuptools;
253 setuptools = basePythonPackages.setuptools;
275
254
276 }
255 }
@@ -1,2060 +1,2060 b''
1 # Generated by pip2nix 0.4.0
1 # Generated by pip2nix 0.4.0
2 # See https://github.com/johbo/pip2nix
2 # See https://github.com/johbo/pip2nix
3
3
4 {
4 {
5 Babel = super.buildPythonPackage {
5 Babel = super.buildPythonPackage {
6 name = "Babel-1.3";
6 name = "Babel-1.3";
7 buildInputs = with self; [];
7 buildInputs = with self; [];
8 doCheck = false;
8 doCheck = false;
9 propagatedBuildInputs = with self; [pytz];
9 propagatedBuildInputs = with self; [pytz];
10 src = fetchurl {
10 src = fetchurl {
11 url = "https://pypi.python.org/packages/33/27/e3978243a03a76398c384c83f7ca879bc6e8f1511233a621fcada135606e/Babel-1.3.tar.gz";
11 url = "https://pypi.python.org/packages/33/27/e3978243a03a76398c384c83f7ca879bc6e8f1511233a621fcada135606e/Babel-1.3.tar.gz";
12 md5 = "5264ceb02717843cbc9ffce8e6e06bdb";
12 md5 = "5264ceb02717843cbc9ffce8e6e06bdb";
13 };
13 };
14 meta = {
14 meta = {
15 license = [ pkgs.lib.licenses.bsdOriginal ];
15 license = [ pkgs.lib.licenses.bsdOriginal ];
16 };
16 };
17 };
17 };
18 Beaker = super.buildPythonPackage {
18 Beaker = super.buildPythonPackage {
19 name = "Beaker-1.9.0";
19 name = "Beaker-1.9.0";
20 buildInputs = with self; [];
20 buildInputs = with self; [];
21 doCheck = false;
21 doCheck = false;
22 propagatedBuildInputs = with self; [funcsigs];
22 propagatedBuildInputs = with self; [funcsigs];
23 src = fetchurl {
23 src = fetchurl {
24 url = "https://pypi.python.org/packages/93/b2/12de6937b06e9615dbb3cb3a1c9af17f133f435bdef59f4ad42032b6eb49/Beaker-1.9.0.tar.gz";
24 url = "https://pypi.python.org/packages/93/b2/12de6937b06e9615dbb3cb3a1c9af17f133f435bdef59f4ad42032b6eb49/Beaker-1.9.0.tar.gz";
25 md5 = "38b3fcdfa24faf97c6cf66991eb54e9c";
25 md5 = "38b3fcdfa24faf97c6cf66991eb54e9c";
26 };
26 };
27 meta = {
27 meta = {
28 license = [ pkgs.lib.licenses.bsdOriginal ];
28 license = [ pkgs.lib.licenses.bsdOriginal ];
29 };
29 };
30 };
30 };
31 CProfileV = super.buildPythonPackage {
31 CProfileV = super.buildPythonPackage {
32 name = "CProfileV-1.0.7";
32 name = "CProfileV-1.0.7";
33 buildInputs = with self; [];
33 buildInputs = with self; [];
34 doCheck = false;
34 doCheck = false;
35 propagatedBuildInputs = with self; [bottle];
35 propagatedBuildInputs = with self; [bottle];
36 src = fetchurl {
36 src = fetchurl {
37 url = "https://pypi.python.org/packages/df/50/d8c1ada7d537c64b0f76453fa31dedb6af6e27b82fcf0331e5f71a4cf98b/CProfileV-1.0.7.tar.gz";
37 url = "https://pypi.python.org/packages/df/50/d8c1ada7d537c64b0f76453fa31dedb6af6e27b82fcf0331e5f71a4cf98b/CProfileV-1.0.7.tar.gz";
38 md5 = "db4c7640438aa3d8887e194c81c7a019";
38 md5 = "db4c7640438aa3d8887e194c81c7a019";
39 };
39 };
40 meta = {
40 meta = {
41 license = [ pkgs.lib.licenses.mit ];
41 license = [ pkgs.lib.licenses.mit ];
42 };
42 };
43 };
43 };
44 Chameleon = super.buildPythonPackage {
44 Chameleon = super.buildPythonPackage {
45 name = "Chameleon-2.24";
45 name = "Chameleon-2.24";
46 buildInputs = with self; [];
46 buildInputs = with self; [];
47 doCheck = false;
47 doCheck = false;
48 propagatedBuildInputs = with self; [];
48 propagatedBuildInputs = with self; [];
49 src = fetchurl {
49 src = fetchurl {
50 url = "https://pypi.python.org/packages/5a/9e/637379ffa13c5172b5c0e704833ffea6bf51cec7567f93fd6e903d53ed74/Chameleon-2.24.tar.gz";
50 url = "https://pypi.python.org/packages/5a/9e/637379ffa13c5172b5c0e704833ffea6bf51cec7567f93fd6e903d53ed74/Chameleon-2.24.tar.gz";
51 md5 = "1b01f1f6533a8a11d0d2f2366dec5342";
51 md5 = "1b01f1f6533a8a11d0d2f2366dec5342";
52 };
52 };
53 meta = {
53 meta = {
54 license = [ { fullName = "BSD-like (http://repoze.org/license.html)"; } ];
54 license = [ { fullName = "BSD-like (http://repoze.org/license.html)"; } ];
55 };
55 };
56 };
56 };
57 FormEncode = super.buildPythonPackage {
57 FormEncode = super.buildPythonPackage {
58 name = "FormEncode-1.2.4";
58 name = "FormEncode-1.2.4";
59 buildInputs = with self; [];
59 buildInputs = with self; [];
60 doCheck = false;
60 doCheck = false;
61 propagatedBuildInputs = with self; [];
61 propagatedBuildInputs = with self; [];
62 src = fetchurl {
62 src = fetchurl {
63 url = "https://pypi.python.org/packages/8e/59/0174271a6f004512e0201188593e6d319db139d14cb7490e488bbb078015/FormEncode-1.2.4.tar.gz";
63 url = "https://pypi.python.org/packages/8e/59/0174271a6f004512e0201188593e6d319db139d14cb7490e488bbb078015/FormEncode-1.2.4.tar.gz";
64 md5 = "6bc17fb9aed8aea198975e888e2077f4";
64 md5 = "6bc17fb9aed8aea198975e888e2077f4";
65 };
65 };
66 meta = {
66 meta = {
67 license = [ pkgs.lib.licenses.psfl ];
67 license = [ pkgs.lib.licenses.psfl ];
68 };
68 };
69 };
69 };
70 Jinja2 = super.buildPythonPackage {
70 Jinja2 = super.buildPythonPackage {
71 name = "Jinja2-2.9.6";
71 name = "Jinja2-2.9.6";
72 buildInputs = with self; [];
72 buildInputs = with self; [];
73 doCheck = false;
73 doCheck = false;
74 propagatedBuildInputs = with self; [MarkupSafe];
74 propagatedBuildInputs = with self; [MarkupSafe];
75 src = fetchurl {
75 src = fetchurl {
76 url = "https://pypi.python.org/packages/90/61/f820ff0076a2599dd39406dcb858ecb239438c02ce706c8e91131ab9c7f1/Jinja2-2.9.6.tar.gz";
76 url = "https://pypi.python.org/packages/90/61/f820ff0076a2599dd39406dcb858ecb239438c02ce706c8e91131ab9c7f1/Jinja2-2.9.6.tar.gz";
77 md5 = "6411537324b4dba0956aaa8109f3c77b";
77 md5 = "6411537324b4dba0956aaa8109f3c77b";
78 };
78 };
79 meta = {
79 meta = {
80 license = [ pkgs.lib.licenses.bsdOriginal ];
80 license = [ pkgs.lib.licenses.bsdOriginal ];
81 };
81 };
82 };
82 };
83 Mako = super.buildPythonPackage {
83 Mako = super.buildPythonPackage {
84 name = "Mako-1.0.7";
84 name = "Mako-1.0.7";
85 buildInputs = with self; [];
85 buildInputs = with self; [];
86 doCheck = false;
86 doCheck = false;
87 propagatedBuildInputs = with self; [MarkupSafe];
87 propagatedBuildInputs = with self; [MarkupSafe];
88 src = fetchurl {
88 src = fetchurl {
89 url = "https://pypi.python.org/packages/eb/f3/67579bb486517c0d49547f9697e36582cd19dafb5df9e687ed8e22de57fa/Mako-1.0.7.tar.gz";
89 url = "https://pypi.python.org/packages/eb/f3/67579bb486517c0d49547f9697e36582cd19dafb5df9e687ed8e22de57fa/Mako-1.0.7.tar.gz";
90 md5 = "5836cc997b1b773ef389bf6629c30e65";
90 md5 = "5836cc997b1b773ef389bf6629c30e65";
91 };
91 };
92 meta = {
92 meta = {
93 license = [ pkgs.lib.licenses.mit ];
93 license = [ pkgs.lib.licenses.mit ];
94 };
94 };
95 };
95 };
96 Markdown = super.buildPythonPackage {
96 Markdown = super.buildPythonPackage {
97 name = "Markdown-2.6.9";
97 name = "Markdown-2.6.9";
98 buildInputs = with self; [];
98 buildInputs = with self; [];
99 doCheck = false;
99 doCheck = false;
100 propagatedBuildInputs = with self; [];
100 propagatedBuildInputs = with self; [];
101 src = fetchurl {
101 src = fetchurl {
102 url = "https://pypi.python.org/packages/29/82/dfe242bcfd9eec0e7bf93a80a8f8d8515a95b980c44f5c0b45606397a423/Markdown-2.6.9.tar.gz";
102 url = "https://pypi.python.org/packages/29/82/dfe242bcfd9eec0e7bf93a80a8f8d8515a95b980c44f5c0b45606397a423/Markdown-2.6.9.tar.gz";
103 md5 = "56547d362a9abcf30955b8950b08b5e3";
103 md5 = "56547d362a9abcf30955b8950b08b5e3";
104 };
104 };
105 meta = {
105 meta = {
106 license = [ pkgs.lib.licenses.bsdOriginal ];
106 license = [ pkgs.lib.licenses.bsdOriginal ];
107 };
107 };
108 };
108 };
109 MarkupSafe = super.buildPythonPackage {
109 MarkupSafe = super.buildPythonPackage {
110 name = "MarkupSafe-1.0";
110 name = "MarkupSafe-1.0";
111 buildInputs = with self; [];
111 buildInputs = with self; [];
112 doCheck = false;
112 doCheck = false;
113 propagatedBuildInputs = with self; [];
113 propagatedBuildInputs = with self; [];
114 src = fetchurl {
114 src = fetchurl {
115 url = "https://pypi.python.org/packages/4d/de/32d741db316d8fdb7680822dd37001ef7a448255de9699ab4bfcbdf4172b/MarkupSafe-1.0.tar.gz";
115 url = "https://pypi.python.org/packages/4d/de/32d741db316d8fdb7680822dd37001ef7a448255de9699ab4bfcbdf4172b/MarkupSafe-1.0.tar.gz";
116 md5 = "2fcedc9284d50e577b5192e8e3578355";
116 md5 = "2fcedc9284d50e577b5192e8e3578355";
117 };
117 };
118 meta = {
118 meta = {
119 license = [ pkgs.lib.licenses.bsdOriginal ];
119 license = [ pkgs.lib.licenses.bsdOriginal ];
120 };
120 };
121 };
121 };
122 MySQL-python = super.buildPythonPackage {
122 MySQL-python = super.buildPythonPackage {
123 name = "MySQL-python-1.2.5";
123 name = "MySQL-python-1.2.5";
124 buildInputs = with self; [];
124 buildInputs = with self; [];
125 doCheck = false;
125 doCheck = false;
126 propagatedBuildInputs = with self; [];
126 propagatedBuildInputs = with self; [];
127 src = fetchurl {
127 src = fetchurl {
128 url = "https://pypi.python.org/packages/a5/e9/51b544da85a36a68debe7a7091f068d802fc515a3a202652828c73453cad/MySQL-python-1.2.5.zip";
128 url = "https://pypi.python.org/packages/a5/e9/51b544da85a36a68debe7a7091f068d802fc515a3a202652828c73453cad/MySQL-python-1.2.5.zip";
129 md5 = "654f75b302db6ed8dc5a898c625e030c";
129 md5 = "654f75b302db6ed8dc5a898c625e030c";
130 };
130 };
131 meta = {
131 meta = {
132 license = [ pkgs.lib.licenses.gpl1 ];
132 license = [ pkgs.lib.licenses.gpl1 ];
133 };
133 };
134 };
134 };
135 Paste = super.buildPythonPackage {
135 Paste = super.buildPythonPackage {
136 name = "Paste-2.0.3";
136 name = "Paste-2.0.3";
137 buildInputs = with self; [];
137 buildInputs = with self; [];
138 doCheck = false;
138 doCheck = false;
139 propagatedBuildInputs = with self; [six];
139 propagatedBuildInputs = with self; [six];
140 src = fetchurl {
140 src = fetchurl {
141 url = "https://pypi.python.org/packages/30/c3/5c2f7c7a02e4f58d4454353fa1c32c94f79fa4e36d07a67c0ac295ea369e/Paste-2.0.3.tar.gz";
141 url = "https://pypi.python.org/packages/30/c3/5c2f7c7a02e4f58d4454353fa1c32c94f79fa4e36d07a67c0ac295ea369e/Paste-2.0.3.tar.gz";
142 md5 = "1231e14eae62fa7ed76e9130b04bc61e";
142 md5 = "1231e14eae62fa7ed76e9130b04bc61e";
143 };
143 };
144 meta = {
144 meta = {
145 license = [ pkgs.lib.licenses.mit ];
145 license = [ pkgs.lib.licenses.mit ];
146 };
146 };
147 };
147 };
148 PasteDeploy = super.buildPythonPackage {
148 PasteDeploy = super.buildPythonPackage {
149 name = "PasteDeploy-1.5.2";
149 name = "PasteDeploy-1.5.2";
150 buildInputs = with self; [];
150 buildInputs = with self; [];
151 doCheck = false;
151 doCheck = false;
152 propagatedBuildInputs = with self; [];
152 propagatedBuildInputs = with self; [];
153 src = fetchurl {
153 src = fetchurl {
154 url = "https://pypi.python.org/packages/0f/90/8e20cdae206c543ea10793cbf4136eb9a8b3f417e04e40a29d72d9922cbd/PasteDeploy-1.5.2.tar.gz";
154 url = "https://pypi.python.org/packages/0f/90/8e20cdae206c543ea10793cbf4136eb9a8b3f417e04e40a29d72d9922cbd/PasteDeploy-1.5.2.tar.gz";
155 md5 = "352b7205c78c8de4987578d19431af3b";
155 md5 = "352b7205c78c8de4987578d19431af3b";
156 };
156 };
157 meta = {
157 meta = {
158 license = [ pkgs.lib.licenses.mit ];
158 license = [ pkgs.lib.licenses.mit ];
159 };
159 };
160 };
160 };
161 PasteScript = super.buildPythonPackage {
161 PasteScript = super.buildPythonPackage {
162 name = "PasteScript-2.0.2";
162 name = "PasteScript-2.0.2";
163 buildInputs = with self; [];
163 buildInputs = with self; [];
164 doCheck = false;
164 doCheck = false;
165 propagatedBuildInputs = with self; [Paste PasteDeploy six];
165 propagatedBuildInputs = with self; [Paste PasteDeploy six];
166 src = fetchurl {
166 src = fetchurl {
167 url = "https://pypi.python.org/packages/e5/f0/78e766c3dcc61a4f3a6f71dd8c95168ae9c7a31722b5663d19c1fdf62cb6/PasteScript-2.0.2.tar.gz";
167 url = "https://pypi.python.org/packages/e5/f0/78e766c3dcc61a4f3a6f71dd8c95168ae9c7a31722b5663d19c1fdf62cb6/PasteScript-2.0.2.tar.gz";
168 md5 = "ccb3045445097192ca71a13b746c77b2";
168 md5 = "ccb3045445097192ca71a13b746c77b2";
169 };
169 };
170 meta = {
170 meta = {
171 license = [ pkgs.lib.licenses.mit ];
171 license = [ pkgs.lib.licenses.mit ];
172 };
172 };
173 };
173 };
174 Pygments = super.buildPythonPackage {
174 Pygments = super.buildPythonPackage {
175 name = "Pygments-2.2.0";
175 name = "Pygments-2.2.0";
176 buildInputs = with self; [];
176 buildInputs = with self; [];
177 doCheck = false;
177 doCheck = false;
178 propagatedBuildInputs = with self; [];
178 propagatedBuildInputs = with self; [];
179 src = fetchurl {
179 src = fetchurl {
180 url = "https://pypi.python.org/packages/71/2a/2e4e77803a8bd6408a2903340ac498cb0a2181811af7c9ec92cb70b0308a/Pygments-2.2.0.tar.gz";
180 url = "https://pypi.python.org/packages/71/2a/2e4e77803a8bd6408a2903340ac498cb0a2181811af7c9ec92cb70b0308a/Pygments-2.2.0.tar.gz";
181 md5 = "13037baca42f16917cbd5ad2fab50844";
181 md5 = "13037baca42f16917cbd5ad2fab50844";
182 };
182 };
183 meta = {
183 meta = {
184 license = [ pkgs.lib.licenses.bsdOriginal ];
184 license = [ pkgs.lib.licenses.bsdOriginal ];
185 };
185 };
186 };
186 };
187 Pylons = super.buildPythonPackage {
187 Routes = super.buildPythonPackage {
188 name = "Pylons-1.0.2.dev20171106";
188 name = "Routes-2.4.1";
189 buildInputs = with self; [];
189 buildInputs = with self; [];
190 doCheck = false;
190 doCheck = false;
191 propagatedBuildInputs = with self; [Routes WebHelpers Beaker Paste PasteDeploy PasteScript FormEncode simplejson decorator nose Mako WebError WebTest Tempita MarkupSafe WebOb];
191 propagatedBuildInputs = with self; [six repoze.lru];
192 src = fetchurl {
192 src = fetchurl {
193 url = "https://code.rhodecode.com/upstream/pylons/archive/707354ee4261b9c10450404fc9852ccea4fd667d.tar.gz?md5=f26633726fa2cd3a340316ee6a5d218f";
193 url = "https://pypi.python.org/packages/33/38/ea827837e68d9c7dde4cff7ec122a93c319f0effc08ce92a17095576603f/Routes-2.4.1.tar.gz";
194 md5 = "f26633726fa2cd3a340316ee6a5d218f";
194 md5 = "c058dff6832941dec47e0d0052548ad8";
195 };
195 };
196 meta = {
196 meta = {
197 license = [ pkgs.lib.licenses.bsdOriginal ];
197 license = [ pkgs.lib.licenses.mit ];
198 };
199 };
200 Routes = super.buildPythonPackage {
201 name = "Routes-1.13";
202 buildInputs = with self; [];
203 doCheck = false;
204 propagatedBuildInputs = with self; [repoze.lru];
205 src = fetchurl {
206 url = "https://pypi.python.org/packages/88/d3/259c3b3cde8837eb9441ab5f574a660e8a4acea8f54a078441d4d2acac1c/Routes-1.13.tar.gz";
207 md5 = "d527b0ab7dd9172b1275a41f97448783";
208 };
209 meta = {
210 license = [ pkgs.lib.licenses.bsdOriginal ];
211 };
198 };
212 };
199 };
213 SQLAlchemy = super.buildPythonPackage {
200 SQLAlchemy = super.buildPythonPackage {
214 name = "SQLAlchemy-1.1.15";
201 name = "SQLAlchemy-1.1.15";
215 buildInputs = with self; [];
202 buildInputs = with self; [];
216 doCheck = false;
203 doCheck = false;
217 propagatedBuildInputs = with self; [];
204 propagatedBuildInputs = with self; [];
218 src = fetchurl {
205 src = fetchurl {
219 url = "https://pypi.python.org/packages/c2/f6/11fcc1ce19a7cb81b1c9377f4e27ce3813265611922e355905e57c44d164/SQLAlchemy-1.1.15.tar.gz";
206 url = "https://pypi.python.org/packages/c2/f6/11fcc1ce19a7cb81b1c9377f4e27ce3813265611922e355905e57c44d164/SQLAlchemy-1.1.15.tar.gz";
220 md5 = "077f9bd3339957f53068b5572a152674";
207 md5 = "077f9bd3339957f53068b5572a152674";
221 };
208 };
222 meta = {
209 meta = {
223 license = [ pkgs.lib.licenses.mit ];
210 license = [ pkgs.lib.licenses.mit ];
224 };
211 };
225 };
212 };
226 Tempita = super.buildPythonPackage {
213 Tempita = super.buildPythonPackage {
227 name = "Tempita-0.5.2";
214 name = "Tempita-0.5.2";
228 buildInputs = with self; [];
215 buildInputs = with self; [];
229 doCheck = false;
216 doCheck = false;
230 propagatedBuildInputs = with self; [];
217 propagatedBuildInputs = with self; [];
231 src = fetchurl {
218 src = fetchurl {
232 url = "https://pypi.python.org/packages/56/c8/8ed6eee83dbddf7b0fc64dd5d4454bc05e6ccaafff47991f73f2894d9ff4/Tempita-0.5.2.tar.gz";
219 url = "https://pypi.python.org/packages/56/c8/8ed6eee83dbddf7b0fc64dd5d4454bc05e6ccaafff47991f73f2894d9ff4/Tempita-0.5.2.tar.gz";
233 md5 = "4c2f17bb9d481821c41b6fbee904cea1";
220 md5 = "4c2f17bb9d481821c41b6fbee904cea1";
234 };
221 };
235 meta = {
222 meta = {
236 license = [ pkgs.lib.licenses.mit ];
223 license = [ pkgs.lib.licenses.mit ];
237 };
224 };
238 };
225 };
239 URLObject = super.buildPythonPackage {
226 URLObject = super.buildPythonPackage {
240 name = "URLObject-2.4.0";
227 name = "URLObject-2.4.0";
241 buildInputs = with self; [];
228 buildInputs = with self; [];
242 doCheck = false;
229 doCheck = false;
243 propagatedBuildInputs = with self; [];
230 propagatedBuildInputs = with self; [];
244 src = fetchurl {
231 src = fetchurl {
245 url = "https://pypi.python.org/packages/cb/b6/e25e58500f9caef85d664bec71ec67c116897bfebf8622c32cb75d1ca199/URLObject-2.4.0.tar.gz";
232 url = "https://pypi.python.org/packages/cb/b6/e25e58500f9caef85d664bec71ec67c116897bfebf8622c32cb75d1ca199/URLObject-2.4.0.tar.gz";
246 md5 = "2ed819738a9f0a3051f31dc9924e3065";
233 md5 = "2ed819738a9f0a3051f31dc9924e3065";
247 };
234 };
248 meta = {
235 meta = {
249 license = [ ];
236 license = [ ];
250 };
237 };
251 };
238 };
252 WebError = super.buildPythonPackage {
239 WebError = super.buildPythonPackage {
253 name = "WebError-0.10.3";
240 name = "WebError-0.10.3";
254 buildInputs = with self; [];
241 buildInputs = with self; [];
255 doCheck = false;
242 doCheck = false;
256 propagatedBuildInputs = with self; [WebOb Tempita Pygments Paste];
243 propagatedBuildInputs = with self; [WebOb Tempita Pygments Paste];
257 src = fetchurl {
244 src = fetchurl {
258 url = "https://pypi.python.org/packages/35/76/e7e5c2ce7e9c7f31b54c1ff295a495886d1279a002557d74dd8957346a79/WebError-0.10.3.tar.gz";
245 url = "https://pypi.python.org/packages/35/76/e7e5c2ce7e9c7f31b54c1ff295a495886d1279a002557d74dd8957346a79/WebError-0.10.3.tar.gz";
259 md5 = "84b9990b0baae6fd440b1e60cdd06f9a";
246 md5 = "84b9990b0baae6fd440b1e60cdd06f9a";
260 };
247 };
261 meta = {
248 meta = {
262 license = [ pkgs.lib.licenses.mit ];
249 license = [ pkgs.lib.licenses.mit ];
263 };
250 };
264 };
251 };
265 WebHelpers = super.buildPythonPackage {
252 WebHelpers = super.buildPythonPackage {
266 name = "WebHelpers-1.3";
253 name = "WebHelpers-1.3";
267 buildInputs = with self; [];
254 buildInputs = with self; [];
268 doCheck = false;
255 doCheck = false;
269 propagatedBuildInputs = with self; [MarkupSafe];
256 propagatedBuildInputs = with self; [MarkupSafe];
270 src = fetchurl {
257 src = fetchurl {
271 url = "https://pypi.python.org/packages/ee/68/4d07672821d514184357f1552f2dad923324f597e722de3b016ca4f7844f/WebHelpers-1.3.tar.gz";
258 url = "https://pypi.python.org/packages/ee/68/4d07672821d514184357f1552f2dad923324f597e722de3b016ca4f7844f/WebHelpers-1.3.tar.gz";
272 md5 = "32749ffadfc40fea51075a7def32588b";
259 md5 = "32749ffadfc40fea51075a7def32588b";
273 };
260 };
274 meta = {
261 meta = {
275 license = [ pkgs.lib.licenses.bsdOriginal ];
262 license = [ pkgs.lib.licenses.bsdOriginal ];
276 };
263 };
277 };
264 };
278 WebHelpers2 = super.buildPythonPackage {
265 WebHelpers2 = super.buildPythonPackage {
279 name = "WebHelpers2-2.0";
266 name = "WebHelpers2-2.0";
280 buildInputs = with self; [];
267 buildInputs = with self; [];
281 doCheck = false;
268 doCheck = false;
282 propagatedBuildInputs = with self; [MarkupSafe six];
269 propagatedBuildInputs = with self; [MarkupSafe six];
283 src = fetchurl {
270 src = fetchurl {
284 url = "https://pypi.python.org/packages/ff/30/56342c6ea522439e3662427c8d7b5e5b390dff4ff2dc92d8afcb8ab68b75/WebHelpers2-2.0.tar.gz";
271 url = "https://pypi.python.org/packages/ff/30/56342c6ea522439e3662427c8d7b5e5b390dff4ff2dc92d8afcb8ab68b75/WebHelpers2-2.0.tar.gz";
285 md5 = "0f6b68d70c12ee0aed48c00b24da13d3";
272 md5 = "0f6b68d70c12ee0aed48c00b24da13d3";
286 };
273 };
287 meta = {
274 meta = {
288 license = [ pkgs.lib.licenses.mit ];
275 license = [ pkgs.lib.licenses.mit ];
289 };
276 };
290 };
277 };
291 WebOb = super.buildPythonPackage {
278 WebOb = super.buildPythonPackage {
292 name = "WebOb-1.7.3";
279 name = "WebOb-1.7.3";
293 buildInputs = with self; [];
280 buildInputs = with self; [];
294 doCheck = false;
281 doCheck = false;
295 propagatedBuildInputs = with self; [];
282 propagatedBuildInputs = with self; [];
296 src = fetchurl {
283 src = fetchurl {
297 url = "https://pypi.python.org/packages/46/87/2f96d8d43b2078fae6e1d33fa86b95c228cebed060f4e3c7576cc44ea83b/WebOb-1.7.3.tar.gz";
284 url = "https://pypi.python.org/packages/46/87/2f96d8d43b2078fae6e1d33fa86b95c228cebed060f4e3c7576cc44ea83b/WebOb-1.7.3.tar.gz";
298 md5 = "350028baffc508e3d23c078118e35316";
285 md5 = "350028baffc508e3d23c078118e35316";
299 };
286 };
300 meta = {
287 meta = {
301 license = [ pkgs.lib.licenses.mit ];
288 license = [ pkgs.lib.licenses.mit ];
302 };
289 };
303 };
290 };
304 WebTest = super.buildPythonPackage {
291 WebTest = super.buildPythonPackage {
305 name = "WebTest-2.0.29";
292 name = "WebTest-2.0.29";
306 buildInputs = with self; [];
293 buildInputs = with self; [];
307 doCheck = false;
294 doCheck = false;
308 propagatedBuildInputs = with self; [six WebOb waitress beautifulsoup4];
295 propagatedBuildInputs = with self; [six WebOb waitress beautifulsoup4];
309 src = fetchurl {
296 src = fetchurl {
310 url = "https://pypi.python.org/packages/94/de/8f94738be649997da99c47b104aa3c3984ecec51a1d8153ed09638253d56/WebTest-2.0.29.tar.gz";
297 url = "https://pypi.python.org/packages/94/de/8f94738be649997da99c47b104aa3c3984ecec51a1d8153ed09638253d56/WebTest-2.0.29.tar.gz";
311 md5 = "30b4cf0d340b9a5335fac4389e6f84fc";
298 md5 = "30b4cf0d340b9a5335fac4389e6f84fc";
312 };
299 };
313 meta = {
300 meta = {
314 license = [ pkgs.lib.licenses.mit ];
301 license = [ pkgs.lib.licenses.mit ];
315 };
302 };
316 };
303 };
317 Whoosh = super.buildPythonPackage {
304 Whoosh = super.buildPythonPackage {
318 name = "Whoosh-2.7.4";
305 name = "Whoosh-2.7.4";
319 buildInputs = with self; [];
306 buildInputs = with self; [];
320 doCheck = false;
307 doCheck = false;
321 propagatedBuildInputs = with self; [];
308 propagatedBuildInputs = with self; [];
322 src = fetchurl {
309 src = fetchurl {
323 url = "https://pypi.python.org/packages/25/2b/6beed2107b148edc1321da0d489afc4617b9ed317ef7b72d4993cad9b684/Whoosh-2.7.4.tar.gz";
310 url = "https://pypi.python.org/packages/25/2b/6beed2107b148edc1321da0d489afc4617b9ed317ef7b72d4993cad9b684/Whoosh-2.7.4.tar.gz";
324 md5 = "c2710105f20b3e29936bd2357383c325";
311 md5 = "c2710105f20b3e29936bd2357383c325";
325 };
312 };
326 meta = {
313 meta = {
327 license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.bsd2 ];
314 license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.bsd2 ];
328 };
315 };
329 };
316 };
330 alembic = super.buildPythonPackage {
317 alembic = super.buildPythonPackage {
331 name = "alembic-0.9.6";
318 name = "alembic-0.9.6";
332 buildInputs = with self; [];
319 buildInputs = with self; [];
333 doCheck = false;
320 doCheck = false;
334 propagatedBuildInputs = with self; [SQLAlchemy Mako python-editor python-dateutil];
321 propagatedBuildInputs = with self; [SQLAlchemy Mako python-editor python-dateutil];
335 src = fetchurl {
322 src = fetchurl {
336 url = "https://pypi.python.org/packages/bf/b3/b28ea715824f8455635ece3c12f59d5d205f98cc378858e414e3aa6ebdbc/alembic-0.9.6.tar.gz";
323 url = "https://pypi.python.org/packages/bf/b3/b28ea715824f8455635ece3c12f59d5d205f98cc378858e414e3aa6ebdbc/alembic-0.9.6.tar.gz";
337 md5 = "fcb096bccc87c8770bd07a04606cb989";
324 md5 = "fcb096bccc87c8770bd07a04606cb989";
338 };
325 };
339 meta = {
326 meta = {
340 license = [ pkgs.lib.licenses.mit ];
327 license = [ pkgs.lib.licenses.mit ];
341 };
328 };
342 };
329 };
330 amqp = super.buildPythonPackage {
331 name = "amqp-2.2.2";
332 buildInputs = with self; [];
333 doCheck = false;
334 propagatedBuildInputs = with self; [vine];
335 src = fetchurl {
336 url = "https://pypi.python.org/packages/e0/70/9ab9ccd8247fb7d2adb717e9f6a0ed358c9e1ab2c349048b0352f9e80ee2/amqp-2.2.2.tar.gz";
337 md5 = "0971a3fd2d635ded45c349cfc17106bd";
338 };
339 meta = {
340 license = [ pkgs.lib.licenses.bsdOriginal ];
341 };
342 };
343 amqplib = super.buildPythonPackage {
343 amqplib = super.buildPythonPackage {
344 name = "amqplib-1.0.2";
344 name = "amqplib-1.0.2";
345 buildInputs = with self; [];
345 buildInputs = with self; [];
346 doCheck = false;
346 doCheck = false;
347 propagatedBuildInputs = with self; [];
347 propagatedBuildInputs = with self; [];
348 src = fetchurl {
348 src = fetchurl {
349 url = "https://pypi.python.org/packages/75/b7/8c2429bf8d92354a0118614f9a4d15e53bc69ebedce534284111de5a0102/amqplib-1.0.2.tgz";
349 url = "https://pypi.python.org/packages/75/b7/8c2429bf8d92354a0118614f9a4d15e53bc69ebedce534284111de5a0102/amqplib-1.0.2.tgz";
350 md5 = "5c92f17fbedd99b2b4a836d4352d1e2f";
350 md5 = "5c92f17fbedd99b2b4a836d4352d1e2f";
351 };
351 };
352 meta = {
352 meta = {
353 license = [ { fullName = "LGPL"; } { fullName = "GNU Library or Lesser General Public License (LGPL)"; } ];
353 license = [ { fullName = "LGPL"; } { fullName = "GNU Library or Lesser General Public License (LGPL)"; } ];
354 };
354 };
355 };
355 };
356 anyjson = super.buildPythonPackage {
357 name = "anyjson-0.3.3";
358 buildInputs = with self; [];
359 doCheck = false;
360 propagatedBuildInputs = with self; [];
361 src = fetchurl {
362 url = "https://pypi.python.org/packages/c3/4d/d4089e1a3dd25b46bebdb55a992b0797cff657b4477bc32ce28038fdecbc/anyjson-0.3.3.tar.gz";
363 md5 = "2ea28d6ec311aeeebaf993cb3008b27c";
364 };
365 meta = {
366 license = [ pkgs.lib.licenses.bsdOriginal ];
367 };
368 };
369 appenlight-client = super.buildPythonPackage {
356 appenlight-client = super.buildPythonPackage {
370 name = "appenlight-client-0.6.22";
357 name = "appenlight-client-0.6.22";
371 buildInputs = with self; [];
358 buildInputs = with self; [];
372 doCheck = false;
359 doCheck = false;
373 propagatedBuildInputs = with self; [WebOb requests six];
360 propagatedBuildInputs = with self; [WebOb requests six];
374 src = fetchurl {
361 src = fetchurl {
375 url = "https://pypi.python.org/packages/73/37/0a64460fa9670b17c061adc433bc8be5079cba21e8b3a92d824adccb12bc/appenlight_client-0.6.22.tar.gz";
362 url = "https://pypi.python.org/packages/73/37/0a64460fa9670b17c061adc433bc8be5079cba21e8b3a92d824adccb12bc/appenlight_client-0.6.22.tar.gz";
376 md5 = "641afc114a9a3b3af4f75b11c70968ee";
363 md5 = "641afc114a9a3b3af4f75b11c70968ee";
377 };
364 };
378 meta = {
365 meta = {
379 license = [ pkgs.lib.licenses.bsdOriginal ];
366 license = [ pkgs.lib.licenses.bsdOriginal ];
380 };
367 };
381 };
368 };
382 authomatic = super.buildPythonPackage {
369 authomatic = super.buildPythonPackage {
383 name = "authomatic-0.1.0.post1";
370 name = "authomatic-0.1.0.post1";
384 buildInputs = with self; [];
371 buildInputs = with self; [];
385 doCheck = false;
372 doCheck = false;
386 propagatedBuildInputs = with self; [];
373 propagatedBuildInputs = with self; [];
387 src = fetchurl {
374 src = fetchurl {
388 url = "https://pypi.python.org/packages/08/1a/8a930461e604c2d5a7a871e1ac59fa82ccf994c32e807230c8d2fb07815a/Authomatic-0.1.0.post1.tar.gz";
375 url = "https://pypi.python.org/packages/08/1a/8a930461e604c2d5a7a871e1ac59fa82ccf994c32e807230c8d2fb07815a/Authomatic-0.1.0.post1.tar.gz";
389 md5 = "be3f3ce08747d776aae6d6cc8dcb49a9";
376 md5 = "be3f3ce08747d776aae6d6cc8dcb49a9";
390 };
377 };
391 meta = {
378 meta = {
392 license = [ pkgs.lib.licenses.mit ];
379 license = [ pkgs.lib.licenses.mit ];
393 };
380 };
394 };
381 };
395 backports.shutil-get-terminal-size = super.buildPythonPackage {
382 backports.shutil-get-terminal-size = super.buildPythonPackage {
396 name = "backports.shutil-get-terminal-size-1.0.0";
383 name = "backports.shutil-get-terminal-size-1.0.0";
397 buildInputs = with self; [];
384 buildInputs = with self; [];
398 doCheck = false;
385 doCheck = false;
399 propagatedBuildInputs = with self; [];
386 propagatedBuildInputs = with self; [];
400 src = fetchurl {
387 src = fetchurl {
401 url = "https://pypi.python.org/packages/ec/9c/368086faa9c016efce5da3e0e13ba392c9db79e3ab740b763fe28620b18b/backports.shutil_get_terminal_size-1.0.0.tar.gz";
388 url = "https://pypi.python.org/packages/ec/9c/368086faa9c016efce5da3e0e13ba392c9db79e3ab740b763fe28620b18b/backports.shutil_get_terminal_size-1.0.0.tar.gz";
402 md5 = "03267762480bd86b50580dc19dff3c66";
389 md5 = "03267762480bd86b50580dc19dff3c66";
403 };
390 };
404 meta = {
391 meta = {
405 license = [ pkgs.lib.licenses.mit ];
392 license = [ pkgs.lib.licenses.mit ];
406 };
393 };
407 };
394 };
408 beautifulsoup4 = super.buildPythonPackage {
395 beautifulsoup4 = super.buildPythonPackage {
409 name = "beautifulsoup4-4.6.0";
396 name = "beautifulsoup4-4.6.0";
410 buildInputs = with self; [];
397 buildInputs = with self; [];
411 doCheck = false;
398 doCheck = false;
412 propagatedBuildInputs = with self; [];
399 propagatedBuildInputs = with self; [];
413 src = fetchurl {
400 src = fetchurl {
414 url = "https://pypi.python.org/packages/fa/8d/1d14391fdaed5abada4e0f63543fef49b8331a34ca60c88bd521bcf7f782/beautifulsoup4-4.6.0.tar.gz";
401 url = "https://pypi.python.org/packages/fa/8d/1d14391fdaed5abada4e0f63543fef49b8331a34ca60c88bd521bcf7f782/beautifulsoup4-4.6.0.tar.gz";
415 md5 = "c17714d0f91a23b708a592cb3c697728";
402 md5 = "c17714d0f91a23b708a592cb3c697728";
416 };
403 };
417 meta = {
404 meta = {
418 license = [ pkgs.lib.licenses.mit ];
405 license = [ pkgs.lib.licenses.mit ];
419 };
406 };
420 };
407 };
408 billiard = super.buildPythonPackage {
409 name = "billiard-3.5.0.3";
410 buildInputs = with self; [];
411 doCheck = false;
412 propagatedBuildInputs = with self; [];
413 src = fetchurl {
414 url = "https://pypi.python.org/packages/39/ac/f5571210cca2e4f4532e38aaff242f26c8654c5e2436bee966c230647ccc/billiard-3.5.0.3.tar.gz";
415 md5 = "113ba481e48400adbf6fbbf59a2f8554";
416 };
417 meta = {
418 license = [ pkgs.lib.licenses.bsdOriginal ];
419 };
420 };
421 bleach = super.buildPythonPackage {
421 bleach = super.buildPythonPackage {
422 name = "bleach-1.5.0";
422 name = "bleach-1.5.0";
423 buildInputs = with self; [];
423 buildInputs = with self; [];
424 doCheck = false;
424 doCheck = false;
425 propagatedBuildInputs = with self; [six html5lib];
425 propagatedBuildInputs = with self; [six html5lib];
426 src = fetchurl {
426 src = fetchurl {
427 url = "https://pypi.python.org/packages/99/00/25a8fce4de102bf6e3cc76bc4ea60685b2fee33bde1b34830c70cacc26a7/bleach-1.5.0.tar.gz";
427 url = "https://pypi.python.org/packages/99/00/25a8fce4de102bf6e3cc76bc4ea60685b2fee33bde1b34830c70cacc26a7/bleach-1.5.0.tar.gz";
428 md5 = "b663300efdf421b3b727b19d7be9c7e7";
428 md5 = "b663300efdf421b3b727b19d7be9c7e7";
429 };
429 };
430 meta = {
430 meta = {
431 license = [ pkgs.lib.licenses.asl20 ];
431 license = [ pkgs.lib.licenses.asl20 ];
432 };
432 };
433 };
433 };
434 bottle = super.buildPythonPackage {
434 bottle = super.buildPythonPackage {
435 name = "bottle-0.12.13";
435 name = "bottle-0.12.13";
436 buildInputs = with self; [];
436 buildInputs = with self; [];
437 doCheck = false;
437 doCheck = false;
438 propagatedBuildInputs = with self; [];
438 propagatedBuildInputs = with self; [];
439 src = fetchurl {
439 src = fetchurl {
440 url = "https://pypi.python.org/packages/bd/99/04dc59ced52a8261ee0f965a8968717a255ea84a36013e527944dbf3468c/bottle-0.12.13.tar.gz";
440 url = "https://pypi.python.org/packages/bd/99/04dc59ced52a8261ee0f965a8968717a255ea84a36013e527944dbf3468c/bottle-0.12.13.tar.gz";
441 md5 = "d2fe1b48c1d49217e78bf326b1cad437";
441 md5 = "d2fe1b48c1d49217e78bf326b1cad437";
442 };
442 };
443 meta = {
443 meta = {
444 license = [ pkgs.lib.licenses.mit ];
444 license = [ pkgs.lib.licenses.mit ];
445 };
445 };
446 };
446 };
447 bumpversion = super.buildPythonPackage {
447 bumpversion = super.buildPythonPackage {
448 name = "bumpversion-0.5.3";
448 name = "bumpversion-0.5.3";
449 buildInputs = with self; [];
449 buildInputs = with self; [];
450 doCheck = false;
450 doCheck = false;
451 propagatedBuildInputs = with self; [];
451 propagatedBuildInputs = with self; [];
452 src = fetchurl {
452 src = fetchurl {
453 url = "https://pypi.python.org/packages/14/41/8c9da3549f8e00c84f0432c3a8cf8ed6898374714676aab91501d48760db/bumpversion-0.5.3.tar.gz";
453 url = "https://pypi.python.org/packages/14/41/8c9da3549f8e00c84f0432c3a8cf8ed6898374714676aab91501d48760db/bumpversion-0.5.3.tar.gz";
454 md5 = "c66a3492eafcf5ad4b024be9fca29820";
454 md5 = "c66a3492eafcf5ad4b024be9fca29820";
455 };
455 };
456 meta = {
456 meta = {
457 license = [ pkgs.lib.licenses.mit ];
457 license = [ pkgs.lib.licenses.mit ];
458 };
458 };
459 };
459 };
460 celery = super.buildPythonPackage {
460 celery = super.buildPythonPackage {
461 name = "celery-2.2.10";
461 name = "celery-4.1.0";
462 buildInputs = with self; [];
462 buildInputs = with self; [];
463 doCheck = false;
463 doCheck = false;
464 propagatedBuildInputs = with self; [python-dateutil anyjson kombu pyparsing];
464 propagatedBuildInputs = with self; [pytz billiard kombu];
465 src = fetchurl {
465 src = fetchurl {
466 url = "https://pypi.python.org/packages/b1/64/860fd50e45844c83442e7953effcddeff66b2851d90b2d784f7201c111b8/celery-2.2.10.tar.gz";
466 url = "https://pypi.python.org/packages/07/65/88a2a45fc80f487872c93121a701a53bbbc3d3d832016876fac84fc8d46a/celery-4.1.0.tar.gz";
467 md5 = "898bc87e54f278055b561316ba73e222";
467 md5 = "db91e1d26936381127f01e150fe3054a";
468 };
468 };
469 meta = {
469 meta = {
470 license = [ pkgs.lib.licenses.bsdOriginal ];
470 license = [ pkgs.lib.licenses.bsdOriginal ];
471 };
471 };
472 };
472 };
473 channelstream = super.buildPythonPackage {
473 channelstream = super.buildPythonPackage {
474 name = "channelstream-0.5.2";
474 name = "channelstream-0.5.2";
475 buildInputs = with self; [];
475 buildInputs = with self; [];
476 doCheck = false;
476 doCheck = false;
477 propagatedBuildInputs = with self; [gevent ws4py pyramid pyramid-jinja2 itsdangerous requests six];
477 propagatedBuildInputs = with self; [gevent ws4py pyramid pyramid-jinja2 itsdangerous requests six];
478 src = fetchurl {
478 src = fetchurl {
479 url = "https://pypi.python.org/packages/2b/31/29a8e085cf5bf97fa88e7b947adabfc581a18a3463adf77fb6dada34a65f/channelstream-0.5.2.tar.gz";
479 url = "https://pypi.python.org/packages/2b/31/29a8e085cf5bf97fa88e7b947adabfc581a18a3463adf77fb6dada34a65f/channelstream-0.5.2.tar.gz";
480 md5 = "1c5eb2a8a405be6f1073da94da6d81d3";
480 md5 = "1c5eb2a8a405be6f1073da94da6d81d3";
481 };
481 };
482 meta = {
482 meta = {
483 license = [ pkgs.lib.licenses.bsdOriginal ];
483 license = [ pkgs.lib.licenses.bsdOriginal ];
484 };
484 };
485 };
485 };
486 click = super.buildPythonPackage {
486 click = super.buildPythonPackage {
487 name = "click-6.6";
487 name = "click-6.6";
488 buildInputs = with self; [];
488 buildInputs = with self; [];
489 doCheck = false;
489 doCheck = false;
490 propagatedBuildInputs = with self; [];
490 propagatedBuildInputs = with self; [];
491 src = fetchurl {
491 src = fetchurl {
492 url = "https://pypi.python.org/packages/7a/00/c14926d8232b36b08218067bcd5853caefb4737cda3f0a47437151344792/click-6.6.tar.gz";
492 url = "https://pypi.python.org/packages/7a/00/c14926d8232b36b08218067bcd5853caefb4737cda3f0a47437151344792/click-6.6.tar.gz";
493 md5 = "d0b09582123605220ad6977175f3e51d";
493 md5 = "d0b09582123605220ad6977175f3e51d";
494 };
494 };
495 meta = {
495 meta = {
496 license = [ pkgs.lib.licenses.bsdOriginal ];
496 license = [ pkgs.lib.licenses.bsdOriginal ];
497 };
497 };
498 };
498 };
499 colander = super.buildPythonPackage {
499 colander = super.buildPythonPackage {
500 name = "colander-1.4";
500 name = "colander-1.4";
501 buildInputs = with self; [];
501 buildInputs = with self; [];
502 doCheck = false;
502 doCheck = false;
503 propagatedBuildInputs = with self; [translationstring iso8601];
503 propagatedBuildInputs = with self; [translationstring iso8601];
504 src = fetchurl {
504 src = fetchurl {
505 url = "https://pypi.python.org/packages/cc/e2/c4e716ac4a426d8ad4dfe306c34f0018a22275d2420815784005bf771c84/colander-1.4.tar.gz";
505 url = "https://pypi.python.org/packages/cc/e2/c4e716ac4a426d8ad4dfe306c34f0018a22275d2420815784005bf771c84/colander-1.4.tar.gz";
506 md5 = "cbb8e403c2ba05aeaa419d51fdb93736";
506 md5 = "cbb8e403c2ba05aeaa419d51fdb93736";
507 };
507 };
508 meta = {
508 meta = {
509 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
509 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
510 };
510 };
511 };
511 };
512 configobj = super.buildPythonPackage {
512 configobj = super.buildPythonPackage {
513 name = "configobj-5.0.6";
513 name = "configobj-5.0.6";
514 buildInputs = with self; [];
514 buildInputs = with self; [];
515 doCheck = false;
515 doCheck = false;
516 propagatedBuildInputs = with self; [six];
516 propagatedBuildInputs = with self; [six];
517 src = fetchurl {
517 src = fetchurl {
518 url = "https://pypi.python.org/packages/64/61/079eb60459c44929e684fa7d9e2fdca403f67d64dd9dbac27296be2e0fab/configobj-5.0.6.tar.gz";
518 url = "https://pypi.python.org/packages/64/61/079eb60459c44929e684fa7d9e2fdca403f67d64dd9dbac27296be2e0fab/configobj-5.0.6.tar.gz";
519 md5 = "e472a3a1c2a67bb0ec9b5d54c13a47d6";
519 md5 = "e472a3a1c2a67bb0ec9b5d54c13a47d6";
520 };
520 };
521 meta = {
521 meta = {
522 license = [ pkgs.lib.licenses.bsdOriginal ];
522 license = [ pkgs.lib.licenses.bsdOriginal ];
523 };
523 };
524 };
524 };
525 configparser = super.buildPythonPackage {
525 configparser = super.buildPythonPackage {
526 name = "configparser-3.5.0";
526 name = "configparser-3.5.0";
527 buildInputs = with self; [];
527 buildInputs = with self; [];
528 doCheck = false;
528 doCheck = false;
529 propagatedBuildInputs = with self; [];
529 propagatedBuildInputs = with self; [];
530 src = fetchurl {
530 src = fetchurl {
531 url = "https://pypi.python.org/packages/7c/69/c2ce7e91c89dc073eb1aa74c0621c3eefbffe8216b3f9af9d3885265c01c/configparser-3.5.0.tar.gz";
531 url = "https://pypi.python.org/packages/7c/69/c2ce7e91c89dc073eb1aa74c0621c3eefbffe8216b3f9af9d3885265c01c/configparser-3.5.0.tar.gz";
532 md5 = "cfdd915a5b7a6c09917a64a573140538";
532 md5 = "cfdd915a5b7a6c09917a64a573140538";
533 };
533 };
534 meta = {
534 meta = {
535 license = [ pkgs.lib.licenses.mit ];
535 license = [ pkgs.lib.licenses.mit ];
536 };
536 };
537 };
537 };
538 cov-core = super.buildPythonPackage {
538 cov-core = super.buildPythonPackage {
539 name = "cov-core-1.15.0";
539 name = "cov-core-1.15.0";
540 buildInputs = with self; [];
540 buildInputs = with self; [];
541 doCheck = false;
541 doCheck = false;
542 propagatedBuildInputs = with self; [coverage];
542 propagatedBuildInputs = with self; [coverage];
543 src = fetchurl {
543 src = fetchurl {
544 url = "https://pypi.python.org/packages/4b/87/13e75a47b4ba1be06f29f6d807ca99638bedc6b57fa491cd3de891ca2923/cov-core-1.15.0.tar.gz";
544 url = "https://pypi.python.org/packages/4b/87/13e75a47b4ba1be06f29f6d807ca99638bedc6b57fa491cd3de891ca2923/cov-core-1.15.0.tar.gz";
545 md5 = "f519d4cb4c4e52856afb14af52919fe6";
545 md5 = "f519d4cb4c4e52856afb14af52919fe6";
546 };
546 };
547 meta = {
547 meta = {
548 license = [ pkgs.lib.licenses.mit ];
548 license = [ pkgs.lib.licenses.mit ];
549 };
549 };
550 };
550 };
551 coverage = super.buildPythonPackage {
551 coverage = super.buildPythonPackage {
552 name = "coverage-3.7.1";
552 name = "coverage-3.7.1";
553 buildInputs = with self; [];
553 buildInputs = with self; [];
554 doCheck = false;
554 doCheck = false;
555 propagatedBuildInputs = with self; [];
555 propagatedBuildInputs = with self; [];
556 src = fetchurl {
556 src = fetchurl {
557 url = "https://pypi.python.org/packages/09/4f/89b06c7fdc09687bca507dc411c342556ef9c5a3b26756137a4878ff19bf/coverage-3.7.1.tar.gz";
557 url = "https://pypi.python.org/packages/09/4f/89b06c7fdc09687bca507dc411c342556ef9c5a3b26756137a4878ff19bf/coverage-3.7.1.tar.gz";
558 md5 = "c47b36ceb17eaff3ecfab3bcd347d0df";
558 md5 = "c47b36ceb17eaff3ecfab3bcd347d0df";
559 };
559 };
560 meta = {
560 meta = {
561 license = [ pkgs.lib.licenses.bsdOriginal ];
561 license = [ pkgs.lib.licenses.bsdOriginal ];
562 };
562 };
563 };
563 };
564 cssselect = super.buildPythonPackage {
564 cssselect = super.buildPythonPackage {
565 name = "cssselect-1.0.1";
565 name = "cssselect-1.0.1";
566 buildInputs = with self; [];
566 buildInputs = with self; [];
567 doCheck = false;
567 doCheck = false;
568 propagatedBuildInputs = with self; [];
568 propagatedBuildInputs = with self; [];
569 src = fetchurl {
569 src = fetchurl {
570 url = "https://pypi.python.org/packages/77/ff/9c865275cd19290feba56344eba570e719efb7ca5b34d67ed12b22ebbb0d/cssselect-1.0.1.tar.gz";
570 url = "https://pypi.python.org/packages/77/ff/9c865275cd19290feba56344eba570e719efb7ca5b34d67ed12b22ebbb0d/cssselect-1.0.1.tar.gz";
571 md5 = "3fa03bf82a9f0b1223c0f1eb1369e139";
571 md5 = "3fa03bf82a9f0b1223c0f1eb1369e139";
572 };
572 };
573 meta = {
573 meta = {
574 license = [ pkgs.lib.licenses.bsdOriginal ];
574 license = [ pkgs.lib.licenses.bsdOriginal ];
575 };
575 };
576 };
576 };
577 decorator = super.buildPythonPackage {
577 decorator = super.buildPythonPackage {
578 name = "decorator-4.1.2";
578 name = "decorator-4.1.2";
579 buildInputs = with self; [];
579 buildInputs = with self; [];
580 doCheck = false;
580 doCheck = false;
581 propagatedBuildInputs = with self; [];
581 propagatedBuildInputs = with self; [];
582 src = fetchurl {
582 src = fetchurl {
583 url = "https://pypi.python.org/packages/bb/e0/f6e41e9091e130bf16d4437dabbac3993908e4d6485ecbc985ef1352db94/decorator-4.1.2.tar.gz";
583 url = "https://pypi.python.org/packages/bb/e0/f6e41e9091e130bf16d4437dabbac3993908e4d6485ecbc985ef1352db94/decorator-4.1.2.tar.gz";
584 md5 = "a0f7f4fe00ae2dde93494d90c192cf8c";
584 md5 = "a0f7f4fe00ae2dde93494d90c192cf8c";
585 };
585 };
586 meta = {
586 meta = {
587 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "new BSD License"; } ];
587 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "new BSD License"; } ];
588 };
588 };
589 };
589 };
590 deform = super.buildPythonPackage {
590 deform = super.buildPythonPackage {
591 name = "deform-2.0.4";
591 name = "deform-2.0.4";
592 buildInputs = with self; [];
592 buildInputs = with self; [];
593 doCheck = false;
593 doCheck = false;
594 propagatedBuildInputs = with self; [Chameleon colander iso8601 peppercorn translationstring zope.deprecation];
594 propagatedBuildInputs = with self; [Chameleon colander iso8601 peppercorn translationstring zope.deprecation];
595 src = fetchurl {
595 src = fetchurl {
596 url = "https://pypi.python.org/packages/66/3b/eefcb07abcab7a97f6665aa2d0cf1af741d9d6e78a2e4657fd2b89f89880/deform-2.0.4.tar.gz";
596 url = "https://pypi.python.org/packages/66/3b/eefcb07abcab7a97f6665aa2d0cf1af741d9d6e78a2e4657fd2b89f89880/deform-2.0.4.tar.gz";
597 md5 = "34756e42cf50dd4b4430809116c4ec0a";
597 md5 = "34756e42cf50dd4b4430809116c4ec0a";
598 };
598 };
599 meta = {
599 meta = {
600 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
600 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
601 };
601 };
602 };
602 };
603 docutils = super.buildPythonPackage {
603 docutils = super.buildPythonPackage {
604 name = "docutils-0.14";
604 name = "docutils-0.14";
605 buildInputs = with self; [];
605 buildInputs = with self; [];
606 doCheck = false;
606 doCheck = false;
607 propagatedBuildInputs = with self; [];
607 propagatedBuildInputs = with self; [];
608 src = fetchurl {
608 src = fetchurl {
609 url = "https://pypi.python.org/packages/84/f4/5771e41fdf52aabebbadecc9381d11dea0fa34e4759b4071244fa094804c/docutils-0.14.tar.gz";
609 url = "https://pypi.python.org/packages/84/f4/5771e41fdf52aabebbadecc9381d11dea0fa34e4759b4071244fa094804c/docutils-0.14.tar.gz";
610 md5 = "c53768d63db3873b7d452833553469de";
610 md5 = "c53768d63db3873b7d452833553469de";
611 };
611 };
612 meta = {
612 meta = {
613 license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.publicDomain pkgs.lib.licenses.gpl1 { fullName = "public domain, Python, 2-Clause BSD, GPL 3 (see COPYING.txt)"; } pkgs.lib.licenses.psfl ];
613 license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.publicDomain pkgs.lib.licenses.gpl1 { fullName = "public domain, Python, 2-Clause BSD, GPL 3 (see COPYING.txt)"; } pkgs.lib.licenses.psfl ];
614 };
614 };
615 };
615 };
616 dogpile.cache = super.buildPythonPackage {
616 dogpile.cache = super.buildPythonPackage {
617 name = "dogpile.cache-0.6.4";
617 name = "dogpile.cache-0.6.4";
618 buildInputs = with self; [];
618 buildInputs = with self; [];
619 doCheck = false;
619 doCheck = false;
620 propagatedBuildInputs = with self; [];
620 propagatedBuildInputs = with self; [];
621 src = fetchurl {
621 src = fetchurl {
622 url = "https://pypi.python.org/packages/b6/3d/35c05ca01c070bb70d9d422f2c4858ecb021b05b21af438fec5ccd7b945c/dogpile.cache-0.6.4.tar.gz";
622 url = "https://pypi.python.org/packages/b6/3d/35c05ca01c070bb70d9d422f2c4858ecb021b05b21af438fec5ccd7b945c/dogpile.cache-0.6.4.tar.gz";
623 md5 = "66e0a6cae6c08cb1ea25f89d0eadfeb0";
623 md5 = "66e0a6cae6c08cb1ea25f89d0eadfeb0";
624 };
624 };
625 meta = {
625 meta = {
626 license = [ pkgs.lib.licenses.bsdOriginal ];
626 license = [ pkgs.lib.licenses.bsdOriginal ];
627 };
627 };
628 };
628 };
629 dogpile.core = super.buildPythonPackage {
629 dogpile.core = super.buildPythonPackage {
630 name = "dogpile.core-0.4.1";
630 name = "dogpile.core-0.4.1";
631 buildInputs = with self; [];
631 buildInputs = with self; [];
632 doCheck = false;
632 doCheck = false;
633 propagatedBuildInputs = with self; [];
633 propagatedBuildInputs = with self; [];
634 src = fetchurl {
634 src = fetchurl {
635 url = "https://pypi.python.org/packages/0e/77/e72abc04c22aedf874301861e5c1e761231c288b5de369c18be8f4b5c9bb/dogpile.core-0.4.1.tar.gz";
635 url = "https://pypi.python.org/packages/0e/77/e72abc04c22aedf874301861e5c1e761231c288b5de369c18be8f4b5c9bb/dogpile.core-0.4.1.tar.gz";
636 md5 = "01cb19f52bba3e95c9b560f39341f045";
636 md5 = "01cb19f52bba3e95c9b560f39341f045";
637 };
637 };
638 meta = {
638 meta = {
639 license = [ pkgs.lib.licenses.bsdOriginal ];
639 license = [ pkgs.lib.licenses.bsdOriginal ];
640 };
640 };
641 };
641 };
642 ecdsa = super.buildPythonPackage {
642 ecdsa = super.buildPythonPackage {
643 name = "ecdsa-0.13";
643 name = "ecdsa-0.13";
644 buildInputs = with self; [];
644 buildInputs = with self; [];
645 doCheck = false;
645 doCheck = false;
646 propagatedBuildInputs = with self; [];
646 propagatedBuildInputs = with self; [];
647 src = fetchurl {
647 src = fetchurl {
648 url = "https://pypi.python.org/packages/f9/e5/99ebb176e47f150ac115ffeda5fedb6a3dbb3c00c74a59fd84ddf12f5857/ecdsa-0.13.tar.gz";
648 url = "https://pypi.python.org/packages/f9/e5/99ebb176e47f150ac115ffeda5fedb6a3dbb3c00c74a59fd84ddf12f5857/ecdsa-0.13.tar.gz";
649 md5 = "1f60eda9cb5c46722856db41a3ae6670";
649 md5 = "1f60eda9cb5c46722856db41a3ae6670";
650 };
650 };
651 meta = {
651 meta = {
652 license = [ pkgs.lib.licenses.mit ];
652 license = [ pkgs.lib.licenses.mit ];
653 };
653 };
654 };
654 };
655 elasticsearch = super.buildPythonPackage {
655 elasticsearch = super.buildPythonPackage {
656 name = "elasticsearch-2.3.0";
656 name = "elasticsearch-2.3.0";
657 buildInputs = with self; [];
657 buildInputs = with self; [];
658 doCheck = false;
658 doCheck = false;
659 propagatedBuildInputs = with self; [urllib3];
659 propagatedBuildInputs = with self; [urllib3];
660 src = fetchurl {
660 src = fetchurl {
661 url = "https://pypi.python.org/packages/10/35/5fd52c5f0b0ee405ed4b5195e8bce44c5e041787680dc7b94b8071cac600/elasticsearch-2.3.0.tar.gz";
661 url = "https://pypi.python.org/packages/10/35/5fd52c5f0b0ee405ed4b5195e8bce44c5e041787680dc7b94b8071cac600/elasticsearch-2.3.0.tar.gz";
662 md5 = "2550f3b51629cf1ef9636608af92c340";
662 md5 = "2550f3b51629cf1ef9636608af92c340";
663 };
663 };
664 meta = {
664 meta = {
665 license = [ pkgs.lib.licenses.asl20 ];
665 license = [ pkgs.lib.licenses.asl20 ];
666 };
666 };
667 };
667 };
668 elasticsearch-dsl = super.buildPythonPackage {
668 elasticsearch-dsl = super.buildPythonPackage {
669 name = "elasticsearch-dsl-2.2.0";
669 name = "elasticsearch-dsl-2.2.0";
670 buildInputs = with self; [];
670 buildInputs = with self; [];
671 doCheck = false;
671 doCheck = false;
672 propagatedBuildInputs = with self; [six python-dateutil elasticsearch];
672 propagatedBuildInputs = with self; [six python-dateutil elasticsearch];
673 src = fetchurl {
673 src = fetchurl {
674 url = "https://pypi.python.org/packages/66/2f/52a086968788e58461641570f45c3207a52d46ebbe9b77dc22b6a8ffda66/elasticsearch-dsl-2.2.0.tar.gz";
674 url = "https://pypi.python.org/packages/66/2f/52a086968788e58461641570f45c3207a52d46ebbe9b77dc22b6a8ffda66/elasticsearch-dsl-2.2.0.tar.gz";
675 md5 = "fa6bd3c87ea3caa8f0f051bc37c53221";
675 md5 = "fa6bd3c87ea3caa8f0f051bc37c53221";
676 };
676 };
677 meta = {
677 meta = {
678 license = [ pkgs.lib.licenses.asl20 ];
678 license = [ pkgs.lib.licenses.asl20 ];
679 };
679 };
680 };
680 };
681 entrypoints = super.buildPythonPackage {
681 entrypoints = super.buildPythonPackage {
682 name = "entrypoints-0.2.2";
682 name = "entrypoints-0.2.2";
683 buildInputs = with self; [];
683 buildInputs = with self; [];
684 doCheck = false;
684 doCheck = false;
685 propagatedBuildInputs = with self; [configparser];
685 propagatedBuildInputs = with self; [configparser];
686 src = fetchurl {
686 src = fetchurl {
687 url = "https://code.rhodecode.com/upstream/entrypoints/archive/96e6d645684e1af3d7df5b5272f3fe85a546b233.tar.gz?md5=7db37771aea9ac9fefe093e5d6987313";
687 url = "https://code.rhodecode.com/upstream/entrypoints/archive/96e6d645684e1af3d7df5b5272f3fe85a546b233.tar.gz?md5=7db37771aea9ac9fefe093e5d6987313";
688 md5 = "7db37771aea9ac9fefe093e5d6987313";
688 md5 = "7db37771aea9ac9fefe093e5d6987313";
689 };
689 };
690 meta = {
690 meta = {
691 license = [ pkgs.lib.licenses.mit ];
691 license = [ pkgs.lib.licenses.mit ];
692 };
692 };
693 };
693 };
694 enum34 = super.buildPythonPackage {
694 enum34 = super.buildPythonPackage {
695 name = "enum34-1.1.6";
695 name = "enum34-1.1.6";
696 buildInputs = with self; [];
696 buildInputs = with self; [];
697 doCheck = false;
697 doCheck = false;
698 propagatedBuildInputs = with self; [];
698 propagatedBuildInputs = with self; [];
699 src = fetchurl {
699 src = fetchurl {
700 url = "https://pypi.python.org/packages/bf/3e/31d502c25302814a7c2f1d3959d2a3b3f78e509002ba91aea64993936876/enum34-1.1.6.tar.gz";
700 url = "https://pypi.python.org/packages/bf/3e/31d502c25302814a7c2f1d3959d2a3b3f78e509002ba91aea64993936876/enum34-1.1.6.tar.gz";
701 md5 = "5f13a0841a61f7fc295c514490d120d0";
701 md5 = "5f13a0841a61f7fc295c514490d120d0";
702 };
702 };
703 meta = {
703 meta = {
704 license = [ pkgs.lib.licenses.bsdOriginal ];
704 license = [ pkgs.lib.licenses.bsdOriginal ];
705 };
705 };
706 };
706 };
707 funcsigs = super.buildPythonPackage {
707 funcsigs = super.buildPythonPackage {
708 name = "funcsigs-1.0.2";
708 name = "funcsigs-1.0.2";
709 buildInputs = with self; [];
709 buildInputs = with self; [];
710 doCheck = false;
710 doCheck = false;
711 propagatedBuildInputs = with self; [];
711 propagatedBuildInputs = with self; [];
712 src = fetchurl {
712 src = fetchurl {
713 url = "https://pypi.python.org/packages/94/4a/db842e7a0545de1cdb0439bb80e6e42dfe82aaeaadd4072f2263a4fbed23/funcsigs-1.0.2.tar.gz";
713 url = "https://pypi.python.org/packages/94/4a/db842e7a0545de1cdb0439bb80e6e42dfe82aaeaadd4072f2263a4fbed23/funcsigs-1.0.2.tar.gz";
714 md5 = "7e583285b1fb8a76305d6d68f4ccc14e";
714 md5 = "7e583285b1fb8a76305d6d68f4ccc14e";
715 };
715 };
716 meta = {
716 meta = {
717 license = [ { fullName = "ASL"; } pkgs.lib.licenses.asl20 ];
717 license = [ { fullName = "ASL"; } pkgs.lib.licenses.asl20 ];
718 };
718 };
719 };
719 };
720 functools32 = super.buildPythonPackage {
720 functools32 = super.buildPythonPackage {
721 name = "functools32-3.2.3.post2";
721 name = "functools32-3.2.3.post2";
722 buildInputs = with self; [];
722 buildInputs = with self; [];
723 doCheck = false;
723 doCheck = false;
724 propagatedBuildInputs = with self; [];
724 propagatedBuildInputs = with self; [];
725 src = fetchurl {
725 src = fetchurl {
726 url = "https://pypi.python.org/packages/5e/1a/0aa2c8195a204a9f51284018562dea77e25511f02fe924fac202fc012172/functools32-3.2.3-2.zip";
726 url = "https://pypi.python.org/packages/5e/1a/0aa2c8195a204a9f51284018562dea77e25511f02fe924fac202fc012172/functools32-3.2.3-2.zip";
727 md5 = "d55232eb132ec779e6893c902a0bc5ad";
727 md5 = "d55232eb132ec779e6893c902a0bc5ad";
728 };
728 };
729 meta = {
729 meta = {
730 license = [ pkgs.lib.licenses.psfl ];
730 license = [ pkgs.lib.licenses.psfl ];
731 };
731 };
732 };
732 };
733 future = super.buildPythonPackage {
733 future = super.buildPythonPackage {
734 name = "future-0.14.3";
734 name = "future-0.14.3";
735 buildInputs = with self; [];
735 buildInputs = with self; [];
736 doCheck = false;
736 doCheck = false;
737 propagatedBuildInputs = with self; [];
737 propagatedBuildInputs = with self; [];
738 src = fetchurl {
738 src = fetchurl {
739 url = "https://pypi.python.org/packages/83/80/8ef3a11a15f8eaafafa0937b20c1b3f73527e69ab6b3fa1cf94a5a96aabb/future-0.14.3.tar.gz";
739 url = "https://pypi.python.org/packages/83/80/8ef3a11a15f8eaafafa0937b20c1b3f73527e69ab6b3fa1cf94a5a96aabb/future-0.14.3.tar.gz";
740 md5 = "e94079b0bd1fc054929e8769fc0f6083";
740 md5 = "e94079b0bd1fc054929e8769fc0f6083";
741 };
741 };
742 meta = {
742 meta = {
743 license = [ { fullName = "OSI Approved"; } pkgs.lib.licenses.mit ];
743 license = [ { fullName = "OSI Approved"; } pkgs.lib.licenses.mit ];
744 };
744 };
745 };
745 };
746 futures = super.buildPythonPackage {
746 futures = super.buildPythonPackage {
747 name = "futures-3.0.2";
747 name = "futures-3.0.2";
748 buildInputs = with self; [];
748 buildInputs = with self; [];
749 doCheck = false;
749 doCheck = false;
750 propagatedBuildInputs = with self; [];
750 propagatedBuildInputs = with self; [];
751 src = fetchurl {
751 src = fetchurl {
752 url = "https://pypi.python.org/packages/f8/e7/fc0fcbeb9193ba2d4de00b065e7fd5aecd0679e93ce95a07322b2b1434f4/futures-3.0.2.tar.gz";
752 url = "https://pypi.python.org/packages/f8/e7/fc0fcbeb9193ba2d4de00b065e7fd5aecd0679e93ce95a07322b2b1434f4/futures-3.0.2.tar.gz";
753 md5 = "42aaf1e4de48d6e871d77dc1f9d96d5a";
753 md5 = "42aaf1e4de48d6e871d77dc1f9d96d5a";
754 };
754 };
755 meta = {
755 meta = {
756 license = [ pkgs.lib.licenses.bsdOriginal ];
756 license = [ pkgs.lib.licenses.bsdOriginal ];
757 };
757 };
758 };
758 };
759 gevent = super.buildPythonPackage {
759 gevent = super.buildPythonPackage {
760 name = "gevent-1.2.2";
760 name = "gevent-1.2.2";
761 buildInputs = with self; [];
761 buildInputs = with self; [];
762 doCheck = false;
762 doCheck = false;
763 propagatedBuildInputs = with self; [greenlet];
763 propagatedBuildInputs = with self; [greenlet];
764 src = fetchurl {
764 src = fetchurl {
765 url = "https://pypi.python.org/packages/1b/92/b111f76e54d2be11375b47b213b56687214f258fd9dae703546d30b837be/gevent-1.2.2.tar.gz";
765 url = "https://pypi.python.org/packages/1b/92/b111f76e54d2be11375b47b213b56687214f258fd9dae703546d30b837be/gevent-1.2.2.tar.gz";
766 md5 = "7f0baf355384fe5ff2ecf66853422554";
766 md5 = "7f0baf355384fe5ff2ecf66853422554";
767 };
767 };
768 meta = {
768 meta = {
769 license = [ pkgs.lib.licenses.mit ];
769 license = [ pkgs.lib.licenses.mit ];
770 };
770 };
771 };
771 };
772 gnureadline = super.buildPythonPackage {
772 gnureadline = super.buildPythonPackage {
773 name = "gnureadline-6.3.8";
773 name = "gnureadline-6.3.8";
774 buildInputs = with self; [];
774 buildInputs = with self; [];
775 doCheck = false;
775 doCheck = false;
776 propagatedBuildInputs = with self; [];
776 propagatedBuildInputs = with self; [];
777 src = fetchurl {
777 src = fetchurl {
778 url = "https://pypi.python.org/packages/50/64/86085c823cd78f9df9d8e33dce0baa71618016f8860460b82cf6610e1eb3/gnureadline-6.3.8.tar.gz";
778 url = "https://pypi.python.org/packages/50/64/86085c823cd78f9df9d8e33dce0baa71618016f8860460b82cf6610e1eb3/gnureadline-6.3.8.tar.gz";
779 md5 = "ba341f4b907250bd1f47dbc06290604f";
779 md5 = "ba341f4b907250bd1f47dbc06290604f";
780 };
780 };
781 meta = {
781 meta = {
782 license = [ { fullName = "GNU General Public License v3 (GPLv3)"; } pkgs.lib.licenses.gpl1 ];
782 license = [ { fullName = "GNU General Public License v3 (GPLv3)"; } pkgs.lib.licenses.gpl1 ];
783 };
783 };
784 };
784 };
785 gprof2dot = super.buildPythonPackage {
785 gprof2dot = super.buildPythonPackage {
786 name = "gprof2dot-2017.9.19";
786 name = "gprof2dot-2017.9.19";
787 buildInputs = with self; [];
787 buildInputs = with self; [];
788 doCheck = false;
788 doCheck = false;
789 propagatedBuildInputs = with self; [];
789 propagatedBuildInputs = with self; [];
790 src = fetchurl {
790 src = fetchurl {
791 url = "https://pypi.python.org/packages/9d/36/f977122502979f3dfb50704979c9ed70e6b620787942b089bf1af15f5aba/gprof2dot-2017.9.19.tar.gz";
791 url = "https://pypi.python.org/packages/9d/36/f977122502979f3dfb50704979c9ed70e6b620787942b089bf1af15f5aba/gprof2dot-2017.9.19.tar.gz";
792 md5 = "cda2d552bb0d0b9f16e6824a9aabd225";
792 md5 = "cda2d552bb0d0b9f16e6824a9aabd225";
793 };
793 };
794 meta = {
794 meta = {
795 license = [ { fullName = "GNU Lesser General Public License v3 or later (LGPLv3+)"; } { fullName = "LGPL"; } ];
795 license = [ { fullName = "GNU Lesser General Public License v3 or later (LGPLv3+)"; } { fullName = "LGPL"; } ];
796 };
796 };
797 };
797 };
798 graphviz = super.buildPythonPackage {
798 graphviz = super.buildPythonPackage {
799 name = "graphviz-0.8.1";
799 name = "graphviz-0.8.1";
800 buildInputs = with self; [];
800 buildInputs = with self; [];
801 doCheck = false;
801 doCheck = false;
802 propagatedBuildInputs = with self; [];
802 propagatedBuildInputs = with self; [];
803 src = fetchurl {
803 src = fetchurl {
804 url = "https://pypi.python.org/packages/a9/a6/ee6721349489a2da6eedd3dba124f2b5ac15ee1e0a7bd4d3cfdc4fff0327/graphviz-0.8.1.zip";
804 url = "https://pypi.python.org/packages/a9/a6/ee6721349489a2da6eedd3dba124f2b5ac15ee1e0a7bd4d3cfdc4fff0327/graphviz-0.8.1.zip";
805 md5 = "88d8efa88c02a735b3659fe0feaf0b96";
805 md5 = "88d8efa88c02a735b3659fe0feaf0b96";
806 };
806 };
807 meta = {
807 meta = {
808 license = [ pkgs.lib.licenses.mit ];
808 license = [ pkgs.lib.licenses.mit ];
809 };
809 };
810 };
810 };
811 greenlet = super.buildPythonPackage {
811 greenlet = super.buildPythonPackage {
812 name = "greenlet-0.4.12";
812 name = "greenlet-0.4.12";
813 buildInputs = with self; [];
813 buildInputs = with self; [];
814 doCheck = false;
814 doCheck = false;
815 propagatedBuildInputs = with self; [];
815 propagatedBuildInputs = with self; [];
816 src = fetchurl {
816 src = fetchurl {
817 url = "https://pypi.python.org/packages/be/76/82af375d98724054b7e273b5d9369346937324f9bcc20980b45b068ef0b0/greenlet-0.4.12.tar.gz";
817 url = "https://pypi.python.org/packages/be/76/82af375d98724054b7e273b5d9369346937324f9bcc20980b45b068ef0b0/greenlet-0.4.12.tar.gz";
818 md5 = "e8637647d58a26c4a1f51ca393e53c00";
818 md5 = "e8637647d58a26c4a1f51ca393e53c00";
819 };
819 };
820 meta = {
820 meta = {
821 license = [ pkgs.lib.licenses.mit ];
821 license = [ pkgs.lib.licenses.mit ];
822 };
822 };
823 };
823 };
824 gunicorn = super.buildPythonPackage {
824 gunicorn = super.buildPythonPackage {
825 name = "gunicorn-19.7.1";
825 name = "gunicorn-19.7.1";
826 buildInputs = with self; [];
826 buildInputs = with self; [];
827 doCheck = false;
827 doCheck = false;
828 propagatedBuildInputs = with self; [];
828 propagatedBuildInputs = with self; [];
829 src = fetchurl {
829 src = fetchurl {
830 url = "https://pypi.python.org/packages/30/3a/10bb213cede0cc4d13ac2263316c872a64bf4c819000c8ccd801f1d5f822/gunicorn-19.7.1.tar.gz";
830 url = "https://pypi.python.org/packages/30/3a/10bb213cede0cc4d13ac2263316c872a64bf4c819000c8ccd801f1d5f822/gunicorn-19.7.1.tar.gz";
831 md5 = "174d3c3cd670a5be0404d84c484e590c";
831 md5 = "174d3c3cd670a5be0404d84c484e590c";
832 };
832 };
833 meta = {
833 meta = {
834 license = [ pkgs.lib.licenses.mit ];
834 license = [ pkgs.lib.licenses.mit ];
835 };
835 };
836 };
836 };
837 html5lib = super.buildPythonPackage {
837 html5lib = super.buildPythonPackage {
838 name = "html5lib-0.9999999";
838 name = "html5lib-0.9999999";
839 buildInputs = with self; [];
839 buildInputs = with self; [];
840 doCheck = false;
840 doCheck = false;
841 propagatedBuildInputs = with self; [six];
841 propagatedBuildInputs = with self; [six];
842 src = fetchurl {
842 src = fetchurl {
843 url = "https://pypi.python.org/packages/ae/ae/bcb60402c60932b32dfaf19bb53870b29eda2cd17551ba5639219fb5ebf9/html5lib-0.9999999.tar.gz";
843 url = "https://pypi.python.org/packages/ae/ae/bcb60402c60932b32dfaf19bb53870b29eda2cd17551ba5639219fb5ebf9/html5lib-0.9999999.tar.gz";
844 md5 = "ef43cb05e9e799f25d65d1135838a96f";
844 md5 = "ef43cb05e9e799f25d65d1135838a96f";
845 };
845 };
846 meta = {
846 meta = {
847 license = [ pkgs.lib.licenses.mit ];
847 license = [ pkgs.lib.licenses.mit ];
848 };
848 };
849 };
849 };
850 hupper = super.buildPythonPackage {
850 hupper = super.buildPythonPackage {
851 name = "hupper-1.0";
851 name = "hupper-1.0";
852 buildInputs = with self; [];
852 buildInputs = with self; [];
853 doCheck = false;
853 doCheck = false;
854 propagatedBuildInputs = with self; [];
854 propagatedBuildInputs = with self; [];
855 src = fetchurl {
855 src = fetchurl {
856 url = "https://pypi.python.org/packages/2e/07/df892c564dc09bb3cf6f6deb976c26adf9117db75ba218cb4353dbc9d826/hupper-1.0.tar.gz";
856 url = "https://pypi.python.org/packages/2e/07/df892c564dc09bb3cf6f6deb976c26adf9117db75ba218cb4353dbc9d826/hupper-1.0.tar.gz";
857 md5 = "26e77da7d5ac5858f59af050d1a6eb5a";
857 md5 = "26e77da7d5ac5858f59af050d1a6eb5a";
858 };
858 };
859 meta = {
859 meta = {
860 license = [ pkgs.lib.licenses.mit ];
860 license = [ pkgs.lib.licenses.mit ];
861 };
861 };
862 };
862 };
863 infrae.cache = super.buildPythonPackage {
863 infrae.cache = super.buildPythonPackage {
864 name = "infrae.cache-1.0.1";
864 name = "infrae.cache-1.0.1";
865 buildInputs = with self; [];
865 buildInputs = with self; [];
866 doCheck = false;
866 doCheck = false;
867 propagatedBuildInputs = with self; [Beaker repoze.lru];
867 propagatedBuildInputs = with self; [Beaker repoze.lru];
868 src = fetchurl {
868 src = fetchurl {
869 url = "https://pypi.python.org/packages/bb/f0/e7d5e984cf6592fd2807dc7bc44a93f9d18e04e6a61f87fdfb2622422d74/infrae.cache-1.0.1.tar.gz";
869 url = "https://pypi.python.org/packages/bb/f0/e7d5e984cf6592fd2807dc7bc44a93f9d18e04e6a61f87fdfb2622422d74/infrae.cache-1.0.1.tar.gz";
870 md5 = "b09076a766747e6ed2a755cc62088e32";
870 md5 = "b09076a766747e6ed2a755cc62088e32";
871 };
871 };
872 meta = {
872 meta = {
873 license = [ pkgs.lib.licenses.zpt21 ];
873 license = [ pkgs.lib.licenses.zpt21 ];
874 };
874 };
875 };
875 };
876 invoke = super.buildPythonPackage {
876 invoke = super.buildPythonPackage {
877 name = "invoke-0.13.0";
877 name = "invoke-0.13.0";
878 buildInputs = with self; [];
878 buildInputs = with self; [];
879 doCheck = false;
879 doCheck = false;
880 propagatedBuildInputs = with self; [];
880 propagatedBuildInputs = with self; [];
881 src = fetchurl {
881 src = fetchurl {
882 url = "https://pypi.python.org/packages/47/bf/d07ef52fa1ac645468858bbac7cb95b246a972a045e821493d17d89c81be/invoke-0.13.0.tar.gz";
882 url = "https://pypi.python.org/packages/47/bf/d07ef52fa1ac645468858bbac7cb95b246a972a045e821493d17d89c81be/invoke-0.13.0.tar.gz";
883 md5 = "c0d1ed4bfb34eaab551662d8cfee6540";
883 md5 = "c0d1ed4bfb34eaab551662d8cfee6540";
884 };
884 };
885 meta = {
885 meta = {
886 license = [ pkgs.lib.licenses.bsdOriginal ];
886 license = [ pkgs.lib.licenses.bsdOriginal ];
887 };
887 };
888 };
888 };
889 ipaddress = super.buildPythonPackage {
889 ipaddress = super.buildPythonPackage {
890 name = "ipaddress-1.0.18";
890 name = "ipaddress-1.0.18";
891 buildInputs = with self; [];
891 buildInputs = with self; [];
892 doCheck = false;
892 doCheck = false;
893 propagatedBuildInputs = with self; [];
893 propagatedBuildInputs = with self; [];
894 src = fetchurl {
894 src = fetchurl {
895 url = "https://pypi.python.org/packages/4e/13/774faf38b445d0b3a844b65747175b2e0500164b7c28d78e34987a5bfe06/ipaddress-1.0.18.tar.gz";
895 url = "https://pypi.python.org/packages/4e/13/774faf38b445d0b3a844b65747175b2e0500164b7c28d78e34987a5bfe06/ipaddress-1.0.18.tar.gz";
896 md5 = "310c2dfd64eb6f0df44aa8c59f2334a7";
896 md5 = "310c2dfd64eb6f0df44aa8c59f2334a7";
897 };
897 };
898 meta = {
898 meta = {
899 license = [ pkgs.lib.licenses.psfl ];
899 license = [ pkgs.lib.licenses.psfl ];
900 };
900 };
901 };
901 };
902 ipdb = super.buildPythonPackage {
902 ipdb = super.buildPythonPackage {
903 name = "ipdb-0.10.3";
903 name = "ipdb-0.10.3";
904 buildInputs = with self; [];
904 buildInputs = with self; [];
905 doCheck = false;
905 doCheck = false;
906 propagatedBuildInputs = with self; [setuptools ipython];
906 propagatedBuildInputs = with self; [setuptools ipython];
907 src = fetchurl {
907 src = fetchurl {
908 url = "https://pypi.python.org/packages/ad/cc/0e7298e1fbf2efd52667c9354a12aa69fb6f796ce230cca03525051718ef/ipdb-0.10.3.tar.gz";
908 url = "https://pypi.python.org/packages/ad/cc/0e7298e1fbf2efd52667c9354a12aa69fb6f796ce230cca03525051718ef/ipdb-0.10.3.tar.gz";
909 md5 = "def1f6ac075d54bdee07e6501263d4fa";
909 md5 = "def1f6ac075d54bdee07e6501263d4fa";
910 };
910 };
911 meta = {
911 meta = {
912 license = [ pkgs.lib.licenses.bsdOriginal ];
912 license = [ pkgs.lib.licenses.bsdOriginal ];
913 };
913 };
914 };
914 };
915 ipython = super.buildPythonPackage {
915 ipython = super.buildPythonPackage {
916 name = "ipython-5.1.0";
916 name = "ipython-5.1.0";
917 buildInputs = with self; [];
917 buildInputs = with self; [];
918 doCheck = false;
918 doCheck = false;
919 propagatedBuildInputs = with self; [setuptools decorator pickleshare simplegeneric traitlets prompt-toolkit Pygments pexpect backports.shutil-get-terminal-size pathlib2 pexpect];
919 propagatedBuildInputs = with self; [setuptools decorator pickleshare simplegeneric traitlets prompt-toolkit Pygments pexpect backports.shutil-get-terminal-size pathlib2 pexpect];
920 src = fetchurl {
920 src = fetchurl {
921 url = "https://pypi.python.org/packages/89/63/a9292f7cd9d0090a0f995e1167f3f17d5889dcbc9a175261719c513b9848/ipython-5.1.0.tar.gz";
921 url = "https://pypi.python.org/packages/89/63/a9292f7cd9d0090a0f995e1167f3f17d5889dcbc9a175261719c513b9848/ipython-5.1.0.tar.gz";
922 md5 = "47c8122420f65b58784cb4b9b4af35e3";
922 md5 = "47c8122420f65b58784cb4b9b4af35e3";
923 };
923 };
924 meta = {
924 meta = {
925 license = [ pkgs.lib.licenses.bsdOriginal ];
925 license = [ pkgs.lib.licenses.bsdOriginal ];
926 };
926 };
927 };
927 };
928 ipython-genutils = super.buildPythonPackage {
928 ipython-genutils = super.buildPythonPackage {
929 name = "ipython-genutils-0.2.0";
929 name = "ipython-genutils-0.2.0";
930 buildInputs = with self; [];
930 buildInputs = with self; [];
931 doCheck = false;
931 doCheck = false;
932 propagatedBuildInputs = with self; [];
932 propagatedBuildInputs = with self; [];
933 src = fetchurl {
933 src = fetchurl {
934 url = "https://pypi.python.org/packages/e8/69/fbeffffc05236398ebfcfb512b6d2511c622871dca1746361006da310399/ipython_genutils-0.2.0.tar.gz";
934 url = "https://pypi.python.org/packages/e8/69/fbeffffc05236398ebfcfb512b6d2511c622871dca1746361006da310399/ipython_genutils-0.2.0.tar.gz";
935 md5 = "5a4f9781f78466da0ea1a648f3e1f79f";
935 md5 = "5a4f9781f78466da0ea1a648f3e1f79f";
936 };
936 };
937 meta = {
937 meta = {
938 license = [ pkgs.lib.licenses.bsdOriginal ];
938 license = [ pkgs.lib.licenses.bsdOriginal ];
939 };
939 };
940 };
940 };
941 iso8601 = super.buildPythonPackage {
941 iso8601 = super.buildPythonPackage {
942 name = "iso8601-0.1.12";
942 name = "iso8601-0.1.12";
943 buildInputs = with self; [];
943 buildInputs = with self; [];
944 doCheck = false;
944 doCheck = false;
945 propagatedBuildInputs = with self; [];
945 propagatedBuildInputs = with self; [];
946 src = fetchurl {
946 src = fetchurl {
947 url = "https://pypi.python.org/packages/45/13/3db24895497345fb44c4248c08b16da34a9eb02643cea2754b21b5ed08b0/iso8601-0.1.12.tar.gz";
947 url = "https://pypi.python.org/packages/45/13/3db24895497345fb44c4248c08b16da34a9eb02643cea2754b21b5ed08b0/iso8601-0.1.12.tar.gz";
948 md5 = "4de940f691c5ea759fb254384c8ddcf6";
948 md5 = "4de940f691c5ea759fb254384c8ddcf6";
949 };
949 };
950 meta = {
950 meta = {
951 license = [ pkgs.lib.licenses.mit ];
951 license = [ pkgs.lib.licenses.mit ];
952 };
952 };
953 };
953 };
954 itsdangerous = super.buildPythonPackage {
954 itsdangerous = super.buildPythonPackage {
955 name = "itsdangerous-0.24";
955 name = "itsdangerous-0.24";
956 buildInputs = with self; [];
956 buildInputs = with self; [];
957 doCheck = false;
957 doCheck = false;
958 propagatedBuildInputs = with self; [];
958 propagatedBuildInputs = with self; [];
959 src = fetchurl {
959 src = fetchurl {
960 url = "https://pypi.python.org/packages/dc/b4/a60bcdba945c00f6d608d8975131ab3f25b22f2bcfe1dab221165194b2d4/itsdangerous-0.24.tar.gz";
960 url = "https://pypi.python.org/packages/dc/b4/a60bcdba945c00f6d608d8975131ab3f25b22f2bcfe1dab221165194b2d4/itsdangerous-0.24.tar.gz";
961 md5 = "a3d55aa79369aef5345c036a8a26307f";
961 md5 = "a3d55aa79369aef5345c036a8a26307f";
962 };
962 };
963 meta = {
963 meta = {
964 license = [ pkgs.lib.licenses.bsdOriginal ];
964 license = [ pkgs.lib.licenses.bsdOriginal ];
965 };
965 };
966 };
966 };
967 jsonschema = super.buildPythonPackage {
967 jsonschema = super.buildPythonPackage {
968 name = "jsonschema-2.6.0";
968 name = "jsonschema-2.6.0";
969 buildInputs = with self; [];
969 buildInputs = with self; [];
970 doCheck = false;
970 doCheck = false;
971 propagatedBuildInputs = with self; [functools32];
971 propagatedBuildInputs = with self; [functools32];
972 src = fetchurl {
972 src = fetchurl {
973 url = "https://pypi.python.org/packages/58/b9/171dbb07e18c6346090a37f03c7e74410a1a56123f847efed59af260a298/jsonschema-2.6.0.tar.gz";
973 url = "https://pypi.python.org/packages/58/b9/171dbb07e18c6346090a37f03c7e74410a1a56123f847efed59af260a298/jsonschema-2.6.0.tar.gz";
974 md5 = "50c6b69a373a8b55ff1e0ec6e78f13f4";
974 md5 = "50c6b69a373a8b55ff1e0ec6e78f13f4";
975 };
975 };
976 meta = {
976 meta = {
977 license = [ pkgs.lib.licenses.mit ];
977 license = [ pkgs.lib.licenses.mit ];
978 };
978 };
979 };
979 };
980 jupyter-client = super.buildPythonPackage {
980 jupyter-client = super.buildPythonPackage {
981 name = "jupyter-client-5.0.0";
981 name = "jupyter-client-5.0.0";
982 buildInputs = with self; [];
982 buildInputs = with self; [];
983 doCheck = false;
983 doCheck = false;
984 propagatedBuildInputs = with self; [traitlets jupyter-core pyzmq python-dateutil];
984 propagatedBuildInputs = with self; [traitlets jupyter-core pyzmq python-dateutil];
985 src = fetchurl {
985 src = fetchurl {
986 url = "https://pypi.python.org/packages/e5/6f/65412ed462202b90134b7e761b0b7e7f949e07a549c1755475333727b3d0/jupyter_client-5.0.0.tar.gz";
986 url = "https://pypi.python.org/packages/e5/6f/65412ed462202b90134b7e761b0b7e7f949e07a549c1755475333727b3d0/jupyter_client-5.0.0.tar.gz";
987 md5 = "1acd331b5c9fb4d79dae9939e79f2426";
987 md5 = "1acd331b5c9fb4d79dae9939e79f2426";
988 };
988 };
989 meta = {
989 meta = {
990 license = [ pkgs.lib.licenses.bsdOriginal ];
990 license = [ pkgs.lib.licenses.bsdOriginal ];
991 };
991 };
992 };
992 };
993 jupyter-core = super.buildPythonPackage {
993 jupyter-core = super.buildPythonPackage {
994 name = "jupyter-core-4.4.0";
994 name = "jupyter-core-4.4.0";
995 buildInputs = with self; [];
995 buildInputs = with self; [];
996 doCheck = false;
996 doCheck = false;
997 propagatedBuildInputs = with self; [traitlets];
997 propagatedBuildInputs = with self; [traitlets];
998 src = fetchurl {
998 src = fetchurl {
999 url = "https://pypi.python.org/packages/b6/2d/2804f4de3a95583f65e5dcb4d7c8c7183124882323758996e867f47e72af/jupyter_core-4.4.0.tar.gz";
999 url = "https://pypi.python.org/packages/b6/2d/2804f4de3a95583f65e5dcb4d7c8c7183124882323758996e867f47e72af/jupyter_core-4.4.0.tar.gz";
1000 md5 = "7829fc07884ed98459e170f217e2a5ba";
1000 md5 = "7829fc07884ed98459e170f217e2a5ba";
1001 };
1001 };
1002 meta = {
1002 meta = {
1003 license = [ pkgs.lib.licenses.bsdOriginal ];
1003 license = [ pkgs.lib.licenses.bsdOriginal ];
1004 };
1004 };
1005 };
1005 };
1006 kombu = super.buildPythonPackage {
1006 kombu = super.buildPythonPackage {
1007 name = "kombu-1.5.1";
1007 name = "kombu-4.1.0";
1008 buildInputs = with self; [];
1008 buildInputs = with self; [];
1009 doCheck = false;
1009 doCheck = false;
1010 propagatedBuildInputs = with self; [anyjson amqplib];
1010 propagatedBuildInputs = with self; [amqp];
1011 src = fetchurl {
1011 src = fetchurl {
1012 url = "https://pypi.python.org/packages/19/53/74bf2a624644b45f0850a638752514fc10a8e1cbd738f10804951a6df3f5/kombu-1.5.1.tar.gz";
1012 url = "https://pypi.python.org/packages/03/5e/1a47d1e543d4943d65330af4e4406049f443878818fb65bfdc651bb93a96/kombu-4.1.0.tar.gz";
1013 md5 = "50662f3c7e9395b3d0721fb75d100b63";
1013 md5 = "2fb2be9fec0e6514231bba23a3779439";
1014 };
1014 };
1015 meta = {
1015 meta = {
1016 license = [ pkgs.lib.licenses.bsdOriginal ];
1016 license = [ pkgs.lib.licenses.bsdOriginal ];
1017 };
1017 };
1018 };
1018 };
1019 lxml = super.buildPythonPackage {
1019 lxml = super.buildPythonPackage {
1020 name = "lxml-3.7.3";
1020 name = "lxml-3.7.3";
1021 buildInputs = with self; [];
1021 buildInputs = with self; [];
1022 doCheck = false;
1022 doCheck = false;
1023 propagatedBuildInputs = with self; [];
1023 propagatedBuildInputs = with self; [];
1024 src = fetchurl {
1024 src = fetchurl {
1025 url = "https://pypi.python.org/packages/39/e8/a8e0b1fa65dd021d48fe21464f71783655f39a41f218293c1c590d54eb82/lxml-3.7.3.tar.gz";
1025 url = "https://pypi.python.org/packages/39/e8/a8e0b1fa65dd021d48fe21464f71783655f39a41f218293c1c590d54eb82/lxml-3.7.3.tar.gz";
1026 md5 = "075692ce442e69bbd604d44e21c02753";
1026 md5 = "075692ce442e69bbd604d44e21c02753";
1027 };
1027 };
1028 meta = {
1028 meta = {
1029 license = [ pkgs.lib.licenses.bsdOriginal ];
1029 license = [ pkgs.lib.licenses.bsdOriginal ];
1030 };
1030 };
1031 };
1031 };
1032 mistune = super.buildPythonPackage {
1032 mistune = super.buildPythonPackage {
1033 name = "mistune-0.8.1";
1033 name = "mistune-0.8.1";
1034 buildInputs = with self; [];
1034 buildInputs = with self; [];
1035 doCheck = false;
1035 doCheck = false;
1036 propagatedBuildInputs = with self; [];
1036 propagatedBuildInputs = with self; [];
1037 src = fetchurl {
1037 src = fetchurl {
1038 url = "https://pypi.python.org/packages/d6/c6/a79d71f6245a8c409a6db3ca2cb86ac657f34b3cfc92b9549b7a9489bfe0/mistune-0.8.1.tar.gz";
1038 url = "https://pypi.python.org/packages/d6/c6/a79d71f6245a8c409a6db3ca2cb86ac657f34b3cfc92b9549b7a9489bfe0/mistune-0.8.1.tar.gz";
1039 md5 = "0fba2b3858a529fc6df675dc7d534bf4";
1039 md5 = "0fba2b3858a529fc6df675dc7d534bf4";
1040 };
1040 };
1041 meta = {
1041 meta = {
1042 license = [ pkgs.lib.licenses.bsdOriginal ];
1042 license = [ pkgs.lib.licenses.bsdOriginal ];
1043 };
1043 };
1044 };
1044 };
1045 mock = super.buildPythonPackage {
1045 mock = super.buildPythonPackage {
1046 name = "mock-1.0.1";
1046 name = "mock-1.0.1";
1047 buildInputs = with self; [];
1047 buildInputs = with self; [];
1048 doCheck = false;
1048 doCheck = false;
1049 propagatedBuildInputs = with self; [];
1049 propagatedBuildInputs = with self; [];
1050 src = fetchurl {
1050 src = fetchurl {
1051 url = "https://pypi.python.org/packages/15/45/30273ee91feb60dabb8fbb2da7868520525f02cf910279b3047182feed80/mock-1.0.1.zip";
1051 url = "https://pypi.python.org/packages/15/45/30273ee91feb60dabb8fbb2da7868520525f02cf910279b3047182feed80/mock-1.0.1.zip";
1052 md5 = "869f08d003c289a97c1a6610faf5e913";
1052 md5 = "869f08d003c289a97c1a6610faf5e913";
1053 };
1053 };
1054 meta = {
1054 meta = {
1055 license = [ pkgs.lib.licenses.bsdOriginal ];
1055 license = [ pkgs.lib.licenses.bsdOriginal ];
1056 };
1056 };
1057 };
1057 };
1058 msgpack-python = super.buildPythonPackage {
1058 msgpack-python = super.buildPythonPackage {
1059 name = "msgpack-python-0.4.8";
1059 name = "msgpack-python-0.4.8";
1060 buildInputs = with self; [];
1060 buildInputs = with self; [];
1061 doCheck = false;
1061 doCheck = false;
1062 propagatedBuildInputs = with self; [];
1062 propagatedBuildInputs = with self; [];
1063 src = fetchurl {
1063 src = fetchurl {
1064 url = "https://pypi.python.org/packages/21/27/8a1d82041c7a2a51fcc73675875a5f9ea06c2663e02fcfeb708be1d081a0/msgpack-python-0.4.8.tar.gz";
1064 url = "https://pypi.python.org/packages/21/27/8a1d82041c7a2a51fcc73675875a5f9ea06c2663e02fcfeb708be1d081a0/msgpack-python-0.4.8.tar.gz";
1065 md5 = "dcd854fb41ee7584ebbf35e049e6be98";
1065 md5 = "dcd854fb41ee7584ebbf35e049e6be98";
1066 };
1066 };
1067 meta = {
1067 meta = {
1068 license = [ pkgs.lib.licenses.asl20 ];
1068 license = [ pkgs.lib.licenses.asl20 ];
1069 };
1069 };
1070 };
1070 };
1071 nbconvert = super.buildPythonPackage {
1071 nbconvert = super.buildPythonPackage {
1072 name = "nbconvert-5.1.1";
1072 name = "nbconvert-5.1.1";
1073 buildInputs = with self; [];
1073 buildInputs = with self; [];
1074 doCheck = false;
1074 doCheck = false;
1075 propagatedBuildInputs = with self; [mistune Jinja2 Pygments traitlets jupyter-core nbformat entrypoints bleach pandocfilters testpath];
1075 propagatedBuildInputs = with self; [mistune Jinja2 Pygments traitlets jupyter-core nbformat entrypoints bleach pandocfilters testpath];
1076 src = fetchurl {
1076 src = fetchurl {
1077 url = "https://pypi.python.org/packages/95/58/df1c91f1658ee5df19097f915a1e71c91fc824a708d82d2b2e35f8b80e9a/nbconvert-5.1.1.tar.gz";
1077 url = "https://pypi.python.org/packages/95/58/df1c91f1658ee5df19097f915a1e71c91fc824a708d82d2b2e35f8b80e9a/nbconvert-5.1.1.tar.gz";
1078 md5 = "d0263fb03a44db2f94eea09a608ed813";
1078 md5 = "d0263fb03a44db2f94eea09a608ed813";
1079 };
1079 };
1080 meta = {
1080 meta = {
1081 license = [ pkgs.lib.licenses.bsdOriginal ];
1081 license = [ pkgs.lib.licenses.bsdOriginal ];
1082 };
1082 };
1083 };
1083 };
1084 nbformat = super.buildPythonPackage {
1084 nbformat = super.buildPythonPackage {
1085 name = "nbformat-4.3.0";
1085 name = "nbformat-4.3.0";
1086 buildInputs = with self; [];
1086 buildInputs = with self; [];
1087 doCheck = false;
1087 doCheck = false;
1088 propagatedBuildInputs = with self; [ipython-genutils traitlets jsonschema jupyter-core];
1088 propagatedBuildInputs = with self; [ipython-genutils traitlets jsonschema jupyter-core];
1089 src = fetchurl {
1089 src = fetchurl {
1090 url = "https://pypi.python.org/packages/f9/c5/89df4abf906f766727f976e170caa85b4f1c1d1feb1f45d716016e68e19f/nbformat-4.3.0.tar.gz";
1090 url = "https://pypi.python.org/packages/f9/c5/89df4abf906f766727f976e170caa85b4f1c1d1feb1f45d716016e68e19f/nbformat-4.3.0.tar.gz";
1091 md5 = "9a00d20425914cd5ba5f97769d9963ca";
1091 md5 = "9a00d20425914cd5ba5f97769d9963ca";
1092 };
1092 };
1093 meta = {
1093 meta = {
1094 license = [ pkgs.lib.licenses.bsdOriginal ];
1094 license = [ pkgs.lib.licenses.bsdOriginal ];
1095 };
1095 };
1096 };
1096 };
1097 nose = super.buildPythonPackage {
1098 name = "nose-1.3.6";
1099 buildInputs = with self; [];
1100 doCheck = false;
1101 propagatedBuildInputs = with self; [];
1102 src = fetchurl {
1103 url = "https://pypi.python.org/packages/70/c7/469e68148d17a0d3db5ed49150242fd70a74a8147b8f3f8b87776e028d99/nose-1.3.6.tar.gz";
1104 md5 = "0ca546d81ca8309080fc80cb389e7a16";
1105 };
1106 meta = {
1107 license = [ { fullName = "GNU Library or Lesser General Public License (LGPL)"; } { fullName = "GNU LGPL"; } ];
1108 };
1109 };
1110 objgraph = super.buildPythonPackage {
1097 objgraph = super.buildPythonPackage {
1111 name = "objgraph-3.1.1";
1098 name = "objgraph-3.1.1";
1112 buildInputs = with self; [];
1099 buildInputs = with self; [];
1113 doCheck = false;
1100 doCheck = false;
1114 propagatedBuildInputs = with self; [graphviz];
1101 propagatedBuildInputs = with self; [graphviz];
1115 src = fetchurl {
1102 src = fetchurl {
1116 url = "https://pypi.python.org/packages/be/58/9ca81a20cc837054e94866df1475d899caaa94f3732b8a46006858b015f7/objgraph-3.1.1.tar.gz";
1103 url = "https://pypi.python.org/packages/be/58/9ca81a20cc837054e94866df1475d899caaa94f3732b8a46006858b015f7/objgraph-3.1.1.tar.gz";
1117 md5 = "253af9944763377877c3678d8aaebb8b";
1104 md5 = "253af9944763377877c3678d8aaebb8b";
1118 };
1105 };
1119 meta = {
1106 meta = {
1120 license = [ pkgs.lib.licenses.mit ];
1107 license = [ pkgs.lib.licenses.mit ];
1121 };
1108 };
1122 };
1109 };
1123 packaging = super.buildPythonPackage {
1110 packaging = super.buildPythonPackage {
1124 name = "packaging-15.2";
1111 name = "packaging-15.2";
1125 buildInputs = with self; [];
1112 buildInputs = with self; [];
1126 doCheck = false;
1113 doCheck = false;
1127 propagatedBuildInputs = with self; [];
1114 propagatedBuildInputs = with self; [];
1128 src = fetchurl {
1115 src = fetchurl {
1129 url = "https://pypi.python.org/packages/24/c4/185da1304f07047dc9e0c46c31db75c0351bd73458ac3efad7da3dbcfbe1/packaging-15.2.tar.gz";
1116 url = "https://pypi.python.org/packages/24/c4/185da1304f07047dc9e0c46c31db75c0351bd73458ac3efad7da3dbcfbe1/packaging-15.2.tar.gz";
1130 md5 = "c16093476f6ced42128bf610e5db3784";
1117 md5 = "c16093476f6ced42128bf610e5db3784";
1131 };
1118 };
1132 meta = {
1119 meta = {
1133 license = [ pkgs.lib.licenses.asl20 ];
1120 license = [ pkgs.lib.licenses.asl20 ];
1134 };
1121 };
1135 };
1122 };
1136 pandocfilters = super.buildPythonPackage {
1123 pandocfilters = super.buildPythonPackage {
1137 name = "pandocfilters-1.4.2";
1124 name = "pandocfilters-1.4.2";
1138 buildInputs = with self; [];
1125 buildInputs = with self; [];
1139 doCheck = false;
1126 doCheck = false;
1140 propagatedBuildInputs = with self; [];
1127 propagatedBuildInputs = with self; [];
1141 src = fetchurl {
1128 src = fetchurl {
1142 url = "https://pypi.python.org/packages/4c/ea/236e2584af67bb6df960832731a6e5325fd4441de001767da328c33368ce/pandocfilters-1.4.2.tar.gz";
1129 url = "https://pypi.python.org/packages/4c/ea/236e2584af67bb6df960832731a6e5325fd4441de001767da328c33368ce/pandocfilters-1.4.2.tar.gz";
1143 md5 = "dc391791ef54c7de1572d7b46b63361f";
1130 md5 = "dc391791ef54c7de1572d7b46b63361f";
1144 };
1131 };
1145 meta = {
1132 meta = {
1146 license = [ pkgs.lib.licenses.bsdOriginal ];
1133 license = [ pkgs.lib.licenses.bsdOriginal ];
1147 };
1134 };
1148 };
1135 };
1149 pathlib2 = super.buildPythonPackage {
1136 pathlib2 = super.buildPythonPackage {
1150 name = "pathlib2-2.3.0";
1137 name = "pathlib2-2.3.0";
1151 buildInputs = with self; [];
1138 buildInputs = with self; [];
1152 doCheck = false;
1139 doCheck = false;
1153 propagatedBuildInputs = with self; [six scandir];
1140 propagatedBuildInputs = with self; [six scandir];
1154 src = fetchurl {
1141 src = fetchurl {
1155 url = "https://pypi.python.org/packages/a1/14/df0deb867c2733f7d857523c10942b3d6612a1b222502fdffa9439943dfb/pathlib2-2.3.0.tar.gz";
1142 url = "https://pypi.python.org/packages/a1/14/df0deb867c2733f7d857523c10942b3d6612a1b222502fdffa9439943dfb/pathlib2-2.3.0.tar.gz";
1156 md5 = "89c90409d11fd5947966b6a30a47d18c";
1143 md5 = "89c90409d11fd5947966b6a30a47d18c";
1157 };
1144 };
1158 meta = {
1145 meta = {
1159 license = [ pkgs.lib.licenses.mit ];
1146 license = [ pkgs.lib.licenses.mit ];
1160 };
1147 };
1161 };
1148 };
1162 peppercorn = super.buildPythonPackage {
1149 peppercorn = super.buildPythonPackage {
1163 name = "peppercorn-0.5";
1150 name = "peppercorn-0.5";
1164 buildInputs = with self; [];
1151 buildInputs = with self; [];
1165 doCheck = false;
1152 doCheck = false;
1166 propagatedBuildInputs = with self; [];
1153 propagatedBuildInputs = with self; [];
1167 src = fetchurl {
1154 src = fetchurl {
1168 url = "https://pypi.python.org/packages/45/ec/a62ec317d1324a01567c5221b420742f094f05ee48097e5157d32be3755c/peppercorn-0.5.tar.gz";
1155 url = "https://pypi.python.org/packages/45/ec/a62ec317d1324a01567c5221b420742f094f05ee48097e5157d32be3755c/peppercorn-0.5.tar.gz";
1169 md5 = "f08efbca5790019ab45d76b7244abd40";
1156 md5 = "f08efbca5790019ab45d76b7244abd40";
1170 };
1157 };
1171 meta = {
1158 meta = {
1172 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1159 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1173 };
1160 };
1174 };
1161 };
1175 pexpect = super.buildPythonPackage {
1162 pexpect = super.buildPythonPackage {
1176 name = "pexpect-4.2.1";
1163 name = "pexpect-4.3.0";
1177 buildInputs = with self; [];
1164 buildInputs = with self; [];
1178 doCheck = false;
1165 doCheck = false;
1179 propagatedBuildInputs = with self; [ptyprocess];
1166 propagatedBuildInputs = with self; [ptyprocess];
1180 src = fetchurl {
1167 src = fetchurl {
1181 url = "https://pypi.python.org/packages/e8/13/d0b0599099d6cd23663043a2a0bb7c61e58c6ba359b2656e6fb000ef5b98/pexpect-4.2.1.tar.gz";
1168 url = "https://pypi.python.org/packages/f8/44/5466c30e49762bb92e442bbdf4472d6904608d211258eb3198a11f0309a4/pexpect-4.3.0.tar.gz";
1182 md5 = "3694410001a99dff83f0b500a1ca1c95";
1169 md5 = "047a486dcd26134b74f2e67046bb61a0";
1183 };
1170 };
1184 meta = {
1171 meta = {
1185 license = [ pkgs.lib.licenses.isc { fullName = "ISC License (ISCL)"; } ];
1172 license = [ pkgs.lib.licenses.isc { fullName = "ISC License (ISCL)"; } ];
1186 };
1173 };
1187 };
1174 };
1188 pickleshare = super.buildPythonPackage {
1175 pickleshare = super.buildPythonPackage {
1189 name = "pickleshare-0.7.4";
1176 name = "pickleshare-0.7.4";
1190 buildInputs = with self; [];
1177 buildInputs = with self; [];
1191 doCheck = false;
1178 doCheck = false;
1192 propagatedBuildInputs = with self; [pathlib2];
1179 propagatedBuildInputs = with self; [pathlib2];
1193 src = fetchurl {
1180 src = fetchurl {
1194 url = "https://pypi.python.org/packages/69/fe/dd137d84daa0fd13a709e448138e310d9ea93070620c9db5454e234af525/pickleshare-0.7.4.tar.gz";
1181 url = "https://pypi.python.org/packages/69/fe/dd137d84daa0fd13a709e448138e310d9ea93070620c9db5454e234af525/pickleshare-0.7.4.tar.gz";
1195 md5 = "6a9e5dd8dfc023031f6b7b3f824cab12";
1182 md5 = "6a9e5dd8dfc023031f6b7b3f824cab12";
1196 };
1183 };
1197 meta = {
1184 meta = {
1198 license = [ pkgs.lib.licenses.mit ];
1185 license = [ pkgs.lib.licenses.mit ];
1199 };
1186 };
1200 };
1187 };
1201 plaster = super.buildPythonPackage {
1188 plaster = super.buildPythonPackage {
1202 name = "plaster-1.0";
1189 name = "plaster-1.0";
1203 buildInputs = with self; [];
1190 buildInputs = with self; [];
1204 doCheck = false;
1191 doCheck = false;
1205 propagatedBuildInputs = with self; [setuptools];
1192 propagatedBuildInputs = with self; [setuptools];
1206 src = fetchurl {
1193 src = fetchurl {
1207 url = "https://pypi.python.org/packages/37/e1/56d04382d718d32751017d32f351214384e529b794084eee20bb52405563/plaster-1.0.tar.gz";
1194 url = "https://pypi.python.org/packages/37/e1/56d04382d718d32751017d32f351214384e529b794084eee20bb52405563/plaster-1.0.tar.gz";
1208 md5 = "80e6beb4760c16fea31754babcc0576e";
1195 md5 = "80e6beb4760c16fea31754babcc0576e";
1209 };
1196 };
1210 meta = {
1197 meta = {
1211 license = [ pkgs.lib.licenses.mit ];
1198 license = [ pkgs.lib.licenses.mit ];
1212 };
1199 };
1213 };
1200 };
1214 plaster-pastedeploy = super.buildPythonPackage {
1201 plaster-pastedeploy = super.buildPythonPackage {
1215 name = "plaster-pastedeploy-0.4.1";
1202 name = "plaster-pastedeploy-0.4.1";
1216 buildInputs = with self; [];
1203 buildInputs = with self; [];
1217 doCheck = false;
1204 doCheck = false;
1218 propagatedBuildInputs = with self; [PasteDeploy plaster];
1205 propagatedBuildInputs = with self; [PasteDeploy plaster];
1219 src = fetchurl {
1206 src = fetchurl {
1220 url = "https://pypi.python.org/packages/9d/6e/f8be01ed41c94e6c54ac97cf2eb142a702aae0c8cce31c846f785e525b40/plaster_pastedeploy-0.4.1.tar.gz";
1207 url = "https://pypi.python.org/packages/9d/6e/f8be01ed41c94e6c54ac97cf2eb142a702aae0c8cce31c846f785e525b40/plaster_pastedeploy-0.4.1.tar.gz";
1221 md5 = "f48d5344b922e56c4978eebf1cd2e0d3";
1208 md5 = "f48d5344b922e56c4978eebf1cd2e0d3";
1222 };
1209 };
1223 meta = {
1210 meta = {
1224 license = [ pkgs.lib.licenses.mit ];
1211 license = [ pkgs.lib.licenses.mit ];
1225 };
1212 };
1226 };
1213 };
1227 prompt-toolkit = super.buildPythonPackage {
1214 prompt-toolkit = super.buildPythonPackage {
1228 name = "prompt-toolkit-1.0.15";
1215 name = "prompt-toolkit-1.0.15";
1229 buildInputs = with self; [];
1216 buildInputs = with self; [];
1230 doCheck = false;
1217 doCheck = false;
1231 propagatedBuildInputs = with self; [six wcwidth];
1218 propagatedBuildInputs = with self; [six wcwidth];
1232 src = fetchurl {
1219 src = fetchurl {
1233 url = "https://pypi.python.org/packages/8a/ad/cf6b128866e78ad6d7f1dc5b7f99885fb813393d9860778b2984582e81b5/prompt_toolkit-1.0.15.tar.gz";
1220 url = "https://pypi.python.org/packages/8a/ad/cf6b128866e78ad6d7f1dc5b7f99885fb813393d9860778b2984582e81b5/prompt_toolkit-1.0.15.tar.gz";
1234 md5 = "8fe70295006dbc8afedd43e5eba99032";
1221 md5 = "8fe70295006dbc8afedd43e5eba99032";
1235 };
1222 };
1236 meta = {
1223 meta = {
1237 license = [ pkgs.lib.licenses.bsdOriginal ];
1224 license = [ pkgs.lib.licenses.bsdOriginal ];
1238 };
1225 };
1239 };
1226 };
1240 psutil = super.buildPythonPackage {
1227 psutil = super.buildPythonPackage {
1241 name = "psutil-5.4.0";
1228 name = "psutil-5.4.0";
1242 buildInputs = with self; [];
1229 buildInputs = with self; [];
1243 doCheck = false;
1230 doCheck = false;
1244 propagatedBuildInputs = with self; [];
1231 propagatedBuildInputs = with self; [];
1245 src = fetchurl {
1232 src = fetchurl {
1246 url = "https://pypi.python.org/packages/8d/96/1fc6468be91521192861966c40bd73fdf8b065eae6d82dd0f870b9825a65/psutil-5.4.0.tar.gz";
1233 url = "https://pypi.python.org/packages/8d/96/1fc6468be91521192861966c40bd73fdf8b065eae6d82dd0f870b9825a65/psutil-5.4.0.tar.gz";
1247 md5 = "01af6219b1e8fcfd53603023967713bf";
1234 md5 = "01af6219b1e8fcfd53603023967713bf";
1248 };
1235 };
1249 meta = {
1236 meta = {
1250 license = [ pkgs.lib.licenses.bsdOriginal ];
1237 license = [ pkgs.lib.licenses.bsdOriginal ];
1251 };
1238 };
1252 };
1239 };
1253 psycopg2 = super.buildPythonPackage {
1240 psycopg2 = super.buildPythonPackage {
1254 name = "psycopg2-2.7.3.2";
1241 name = "psycopg2-2.7.3.2";
1255 buildInputs = with self; [];
1242 buildInputs = with self; [];
1256 doCheck = false;
1243 doCheck = false;
1257 propagatedBuildInputs = with self; [];
1244 propagatedBuildInputs = with self; [];
1258 src = fetchurl {
1245 src = fetchurl {
1259 url = "https://pypi.python.org/packages/dd/47/000b405d73ca22980684fd7bd3318690cc03cfa3b2ae1c5b7fff8050b28a/psycopg2-2.7.3.2.tar.gz";
1246 url = "https://pypi.python.org/packages/dd/47/000b405d73ca22980684fd7bd3318690cc03cfa3b2ae1c5b7fff8050b28a/psycopg2-2.7.3.2.tar.gz";
1260 md5 = "8114e672d5f23fa5329874a4314fbd6f";
1247 md5 = "8114e672d5f23fa5329874a4314fbd6f";
1261 };
1248 };
1262 meta = {
1249 meta = {
1263 license = [ pkgs.lib.licenses.zpt21 { fullName = "GNU Library or Lesser General Public License (LGPL)"; } { fullName = "LGPL with exceptions or ZPL"; } ];
1250 license = [ pkgs.lib.licenses.zpt21 { fullName = "GNU Library or Lesser General Public License (LGPL)"; } { fullName = "LGPL with exceptions or ZPL"; } ];
1264 };
1251 };
1265 };
1252 };
1266 ptyprocess = super.buildPythonPackage {
1253 ptyprocess = super.buildPythonPackage {
1267 name = "ptyprocess-0.5.2";
1254 name = "ptyprocess-0.5.2";
1268 buildInputs = with self; [];
1255 buildInputs = with self; [];
1269 doCheck = false;
1256 doCheck = false;
1270 propagatedBuildInputs = with self; [];
1257 propagatedBuildInputs = with self; [];
1271 src = fetchurl {
1258 src = fetchurl {
1272 url = "https://pypi.python.org/packages/51/83/5d07dc35534640b06f9d9f1a1d2bc2513fb9cc7595a1b0e28ae5477056ce/ptyprocess-0.5.2.tar.gz";
1259 url = "https://pypi.python.org/packages/51/83/5d07dc35534640b06f9d9f1a1d2bc2513fb9cc7595a1b0e28ae5477056ce/ptyprocess-0.5.2.tar.gz";
1273 md5 = "d3b8febae1b8c53b054bd818d0bb8665";
1260 md5 = "d3b8febae1b8c53b054bd818d0bb8665";
1274 };
1261 };
1275 meta = {
1262 meta = {
1276 license = [ ];
1263 license = [ ];
1277 };
1264 };
1278 };
1265 };
1279 py = super.buildPythonPackage {
1266 py = super.buildPythonPackage {
1280 name = "py-1.4.34";
1267 name = "py-1.4.34";
1281 buildInputs = with self; [];
1268 buildInputs = with self; [];
1282 doCheck = false;
1269 doCheck = false;
1283 propagatedBuildInputs = with self; [];
1270 propagatedBuildInputs = with self; [];
1284 src = fetchurl {
1271 src = fetchurl {
1285 url = "https://pypi.python.org/packages/68/35/58572278f1c097b403879c1e9369069633d1cbad5239b9057944bb764782/py-1.4.34.tar.gz";
1272 url = "https://pypi.python.org/packages/68/35/58572278f1c097b403879c1e9369069633d1cbad5239b9057944bb764782/py-1.4.34.tar.gz";
1286 md5 = "d9c3d8f734b0819ff48e355d77bf1730";
1273 md5 = "d9c3d8f734b0819ff48e355d77bf1730";
1287 };
1274 };
1288 meta = {
1275 meta = {
1289 license = [ pkgs.lib.licenses.mit ];
1276 license = [ pkgs.lib.licenses.mit ];
1290 };
1277 };
1291 };
1278 };
1292 py-bcrypt = super.buildPythonPackage {
1279 py-bcrypt = super.buildPythonPackage {
1293 name = "py-bcrypt-0.4";
1280 name = "py-bcrypt-0.4";
1294 buildInputs = with self; [];
1281 buildInputs = with self; [];
1295 doCheck = false;
1282 doCheck = false;
1296 propagatedBuildInputs = with self; [];
1283 propagatedBuildInputs = with self; [];
1297 src = fetchurl {
1284 src = fetchurl {
1298 url = "https://pypi.python.org/packages/68/b1/1c3068c5c4d2e35c48b38dcc865301ebfdf45f54507086ac65ced1fd3b3d/py-bcrypt-0.4.tar.gz";
1285 url = "https://pypi.python.org/packages/68/b1/1c3068c5c4d2e35c48b38dcc865301ebfdf45f54507086ac65ced1fd3b3d/py-bcrypt-0.4.tar.gz";
1299 md5 = "dd8b367d6b716a2ea2e72392525f4e36";
1286 md5 = "dd8b367d6b716a2ea2e72392525f4e36";
1300 };
1287 };
1301 meta = {
1288 meta = {
1302 license = [ pkgs.lib.licenses.bsdOriginal ];
1289 license = [ pkgs.lib.licenses.bsdOriginal ];
1303 };
1290 };
1304 };
1291 };
1305 py-gfm = super.buildPythonPackage {
1292 py-gfm = super.buildPythonPackage {
1306 name = "py-gfm-0.1.3";
1293 name = "py-gfm-0.1.3";
1307 buildInputs = with self; [];
1294 buildInputs = with self; [];
1308 doCheck = false;
1295 doCheck = false;
1309 propagatedBuildInputs = with self; [setuptools Markdown];
1296 propagatedBuildInputs = with self; [setuptools Markdown];
1310 src = fetchurl {
1297 src = fetchurl {
1311 url = "https://pypi.python.org/packages/12/e4/6b3d8678da04f97d7490d8264d8de51c2dc9fb91209ccee9c515c95e14c5/py-gfm-0.1.3.tar.gz";
1298 url = "https://pypi.python.org/packages/12/e4/6b3d8678da04f97d7490d8264d8de51c2dc9fb91209ccee9c515c95e14c5/py-gfm-0.1.3.tar.gz";
1312 md5 = "e588d9e69640a241b97e2c59c22527a6";
1299 md5 = "e588d9e69640a241b97e2c59c22527a6";
1313 };
1300 };
1314 meta = {
1301 meta = {
1315 license = [ pkgs.lib.licenses.bsdOriginal ];
1302 license = [ pkgs.lib.licenses.bsdOriginal ];
1316 };
1303 };
1317 };
1304 };
1318 pycrypto = super.buildPythonPackage {
1305 pycrypto = super.buildPythonPackage {
1319 name = "pycrypto-2.6.1";
1306 name = "pycrypto-2.6.1";
1320 buildInputs = with self; [];
1307 buildInputs = with self; [];
1321 doCheck = false;
1308 doCheck = false;
1322 propagatedBuildInputs = with self; [];
1309 propagatedBuildInputs = with self; [];
1323 src = fetchurl {
1310 src = fetchurl {
1324 url = "https://pypi.python.org/packages/60/db/645aa9af249f059cc3a368b118de33889219e0362141e75d4eaf6f80f163/pycrypto-2.6.1.tar.gz";
1311 url = "https://pypi.python.org/packages/60/db/645aa9af249f059cc3a368b118de33889219e0362141e75d4eaf6f80f163/pycrypto-2.6.1.tar.gz";
1325 md5 = "55a61a054aa66812daf5161a0d5d7eda";
1312 md5 = "55a61a054aa66812daf5161a0d5d7eda";
1326 };
1313 };
1327 meta = {
1314 meta = {
1328 license = [ pkgs.lib.licenses.publicDomain ];
1315 license = [ pkgs.lib.licenses.publicDomain ];
1329 };
1316 };
1330 };
1317 };
1331 pycurl = super.buildPythonPackage {
1318 pycurl = super.buildPythonPackage {
1332 name = "pycurl-7.19.5";
1319 name = "pycurl-7.19.5";
1333 buildInputs = with self; [];
1320 buildInputs = with self; [];
1334 doCheck = false;
1321 doCheck = false;
1335 propagatedBuildInputs = with self; [];
1322 propagatedBuildInputs = with self; [];
1336 src = fetchurl {
1323 src = fetchurl {
1337 url = "https://pypi.python.org/packages/6c/48/13bad289ef6f4869b1d8fc11ae54de8cfb3cc4a2eb9f7419c506f763be46/pycurl-7.19.5.tar.gz";
1324 url = "https://pypi.python.org/packages/6c/48/13bad289ef6f4869b1d8fc11ae54de8cfb3cc4a2eb9f7419c506f763be46/pycurl-7.19.5.tar.gz";
1338 md5 = "47b4eac84118e2606658122104e62072";
1325 md5 = "47b4eac84118e2606658122104e62072";
1339 };
1326 };
1340 meta = {
1327 meta = {
1341 license = [ pkgs.lib.licenses.mit { fullName = "LGPL/MIT"; } { fullName = "GNU Library or Lesser General Public License (LGPL)"; } ];
1328 license = [ pkgs.lib.licenses.mit { fullName = "LGPL/MIT"; } { fullName = "GNU Library or Lesser General Public License (LGPL)"; } ];
1342 };
1329 };
1343 };
1330 };
1344 pyflakes = super.buildPythonPackage {
1331 pyflakes = super.buildPythonPackage {
1345 name = "pyflakes-0.8.1";
1332 name = "pyflakes-0.8.1";
1346 buildInputs = with self; [];
1333 buildInputs = with self; [];
1347 doCheck = false;
1334 doCheck = false;
1348 propagatedBuildInputs = with self; [];
1335 propagatedBuildInputs = with self; [];
1349 src = fetchurl {
1336 src = fetchurl {
1350 url = "https://pypi.python.org/packages/75/22/a90ec0252f4f87f3ffb6336504de71fe16a49d69c4538dae2f12b9360a38/pyflakes-0.8.1.tar.gz";
1337 url = "https://pypi.python.org/packages/75/22/a90ec0252f4f87f3ffb6336504de71fe16a49d69c4538dae2f12b9360a38/pyflakes-0.8.1.tar.gz";
1351 md5 = "905fe91ad14b912807e8fdc2ac2e2c23";
1338 md5 = "905fe91ad14b912807e8fdc2ac2e2c23";
1352 };
1339 };
1353 meta = {
1340 meta = {
1354 license = [ pkgs.lib.licenses.mit ];
1341 license = [ pkgs.lib.licenses.mit ];
1355 };
1342 };
1356 };
1343 };
1357 pygments-markdown-lexer = super.buildPythonPackage {
1344 pygments-markdown-lexer = super.buildPythonPackage {
1358 name = "pygments-markdown-lexer-0.1.0.dev39";
1345 name = "pygments-markdown-lexer-0.1.0.dev39";
1359 buildInputs = with self; [];
1346 buildInputs = with self; [];
1360 doCheck = false;
1347 doCheck = false;
1361 propagatedBuildInputs = with self; [Pygments];
1348 propagatedBuildInputs = with self; [Pygments];
1362 src = fetchurl {
1349 src = fetchurl {
1363 url = "https://pypi.python.org/packages/c3/12/674cdee66635d638cedb2c5d9c85ce507b7b2f91bdba29e482f1b1160ff6/pygments-markdown-lexer-0.1.0.dev39.zip";
1350 url = "https://pypi.python.org/packages/c3/12/674cdee66635d638cedb2c5d9c85ce507b7b2f91bdba29e482f1b1160ff6/pygments-markdown-lexer-0.1.0.dev39.zip";
1364 md5 = "6360fe0f6d1f896e35b7a0142ce6459c";
1351 md5 = "6360fe0f6d1f896e35b7a0142ce6459c";
1365 };
1352 };
1366 meta = {
1353 meta = {
1367 license = [ pkgs.lib.licenses.asl20 ];
1354 license = [ pkgs.lib.licenses.asl20 ];
1368 };
1355 };
1369 };
1356 };
1370 pyparsing = super.buildPythonPackage {
1357 pyparsing = super.buildPythonPackage {
1371 name = "pyparsing-1.5.7";
1358 name = "pyparsing-1.5.7";
1372 buildInputs = with self; [];
1359 buildInputs = with self; [];
1373 doCheck = false;
1360 doCheck = false;
1374 propagatedBuildInputs = with self; [];
1361 propagatedBuildInputs = with self; [];
1375 src = fetchurl {
1362 src = fetchurl {
1376 url = "https://pypi.python.org/packages/2e/26/e8fb5b4256a5f5036be7ce115ef8db8d06bc537becfbdc46c6af008314ee/pyparsing-1.5.7.zip";
1363 url = "https://pypi.python.org/packages/2e/26/e8fb5b4256a5f5036be7ce115ef8db8d06bc537becfbdc46c6af008314ee/pyparsing-1.5.7.zip";
1377 md5 = "b86854857a368d6ccb4d5b6e76d0637f";
1364 md5 = "b86854857a368d6ccb4d5b6e76d0637f";
1378 };
1365 };
1379 meta = {
1366 meta = {
1380 license = [ pkgs.lib.licenses.mit ];
1367 license = [ pkgs.lib.licenses.mit ];
1381 };
1368 };
1382 };
1369 };
1383 pyramid = super.buildPythonPackage {
1370 pyramid = super.buildPythonPackage {
1384 name = "pyramid-1.9.1";
1371 name = "pyramid-1.9.1";
1385 buildInputs = with self; [];
1372 buildInputs = with self; [];
1386 doCheck = false;
1373 doCheck = false;
1387 propagatedBuildInputs = with self; [setuptools WebOb repoze.lru zope.interface zope.deprecation venusian translationstring PasteDeploy plaster plaster-pastedeploy hupper];
1374 propagatedBuildInputs = with self; [setuptools WebOb repoze.lru zope.interface zope.deprecation venusian translationstring PasteDeploy plaster plaster-pastedeploy hupper];
1388 src = fetchurl {
1375 src = fetchurl {
1389 url = "https://pypi.python.org/packages/9a/57/73447be9e7d0512d601e3f0a1fb9d7d1efb941911f49efdfe036d2826507/pyramid-1.9.1.tar.gz";
1376 url = "https://pypi.python.org/packages/9a/57/73447be9e7d0512d601e3f0a1fb9d7d1efb941911f49efdfe036d2826507/pyramid-1.9.1.tar.gz";
1390 md5 = "0163e19c58c2d12976a3b6fdb57e052d";
1377 md5 = "0163e19c58c2d12976a3b6fdb57e052d";
1391 };
1378 };
1392 meta = {
1379 meta = {
1393 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1380 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1394 };
1381 };
1395 };
1382 };
1396 pyramid-beaker = super.buildPythonPackage {
1383 pyramid-beaker = super.buildPythonPackage {
1397 name = "pyramid-beaker-0.8";
1384 name = "pyramid-beaker-0.8";
1398 buildInputs = with self; [];
1385 buildInputs = with self; [];
1399 doCheck = false;
1386 doCheck = false;
1400 propagatedBuildInputs = with self; [pyramid Beaker];
1387 propagatedBuildInputs = with self; [pyramid Beaker];
1401 src = fetchurl {
1388 src = fetchurl {
1402 url = "https://pypi.python.org/packages/d9/6e/b85426e00fd3d57f4545f74e1c3828552d8700f13ededeef9233f7bca8be/pyramid_beaker-0.8.tar.gz";
1389 url = "https://pypi.python.org/packages/d9/6e/b85426e00fd3d57f4545f74e1c3828552d8700f13ededeef9233f7bca8be/pyramid_beaker-0.8.tar.gz";
1403 md5 = "22f14be31b06549f80890e2c63a93834";
1390 md5 = "22f14be31b06549f80890e2c63a93834";
1404 };
1391 };
1405 meta = {
1392 meta = {
1406 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1393 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1407 };
1394 };
1408 };
1395 };
1409 pyramid-debugtoolbar = super.buildPythonPackage {
1396 pyramid-debugtoolbar = super.buildPythonPackage {
1410 name = "pyramid-debugtoolbar-4.3";
1397 name = "pyramid-debugtoolbar-4.3";
1411 buildInputs = with self; [];
1398 buildInputs = with self; [];
1412 doCheck = false;
1399 doCheck = false;
1413 propagatedBuildInputs = with self; [pyramid pyramid-mako repoze.lru Pygments ipaddress];
1400 propagatedBuildInputs = with self; [pyramid pyramid-mako repoze.lru Pygments ipaddress];
1414 src = fetchurl {
1401 src = fetchurl {
1415 url = "https://pypi.python.org/packages/a4/40/f09d8800bfc3c09bdb6c95f37bb61c890dc62c19c4e7caa304da7aa77403/pyramid_debugtoolbar-4.3.tar.gz";
1402 url = "https://pypi.python.org/packages/a4/40/f09d8800bfc3c09bdb6c95f37bb61c890dc62c19c4e7caa304da7aa77403/pyramid_debugtoolbar-4.3.tar.gz";
1416 md5 = "9c49029e9f0695130499ef6416ffaaf8";
1403 md5 = "9c49029e9f0695130499ef6416ffaaf8";
1417 };
1404 };
1418 meta = {
1405 meta = {
1419 license = [ { fullName = "Repoze Public License"; } pkgs.lib.licenses.bsdOriginal ];
1406 license = [ { fullName = "Repoze Public License"; } pkgs.lib.licenses.bsdOriginal ];
1420 };
1407 };
1421 };
1408 };
1422 pyramid-jinja2 = super.buildPythonPackage {
1409 pyramid-jinja2 = super.buildPythonPackage {
1423 name = "pyramid-jinja2-2.7";
1410 name = "pyramid-jinja2-2.7";
1424 buildInputs = with self; [];
1411 buildInputs = with self; [];
1425 doCheck = false;
1412 doCheck = false;
1426 propagatedBuildInputs = with self; [pyramid zope.deprecation Jinja2 MarkupSafe];
1413 propagatedBuildInputs = with self; [pyramid zope.deprecation Jinja2 MarkupSafe];
1427 src = fetchurl {
1414 src = fetchurl {
1428 url = "https://pypi.python.org/packages/d8/80/d60a7233823de22ce77bd864a8a83736a1fe8b49884b08303a2e68b2c853/pyramid_jinja2-2.7.tar.gz";
1415 url = "https://pypi.python.org/packages/d8/80/d60a7233823de22ce77bd864a8a83736a1fe8b49884b08303a2e68b2c853/pyramid_jinja2-2.7.tar.gz";
1429 md5 = "c2f8b2cd7b73a6f1d9a311fcfaf4fb92";
1416 md5 = "c2f8b2cd7b73a6f1d9a311fcfaf4fb92";
1430 };
1417 };
1431 meta = {
1418 meta = {
1432 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1419 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1433 };
1420 };
1434 };
1421 };
1435 pyramid-mako = super.buildPythonPackage {
1422 pyramid-mako = super.buildPythonPackage {
1436 name = "pyramid-mako-1.0.2";
1423 name = "pyramid-mako-1.0.2";
1437 buildInputs = with self; [];
1424 buildInputs = with self; [];
1438 doCheck = false;
1425 doCheck = false;
1439 propagatedBuildInputs = with self; [pyramid Mako];
1426 propagatedBuildInputs = with self; [pyramid Mako];
1440 src = fetchurl {
1427 src = fetchurl {
1441 url = "https://pypi.python.org/packages/f1/92/7e69bcf09676d286a71cb3bbb887b16595b96f9ba7adbdc239ffdd4b1eb9/pyramid_mako-1.0.2.tar.gz";
1428 url = "https://pypi.python.org/packages/f1/92/7e69bcf09676d286a71cb3bbb887b16595b96f9ba7adbdc239ffdd4b1eb9/pyramid_mako-1.0.2.tar.gz";
1442 md5 = "ee25343a97eb76bd90abdc2a774eb48a";
1429 md5 = "ee25343a97eb76bd90abdc2a774eb48a";
1443 };
1430 };
1444 meta = {
1431 meta = {
1445 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1432 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1446 };
1433 };
1447 };
1434 };
1448 pysqlite = super.buildPythonPackage {
1435 pysqlite = super.buildPythonPackage {
1449 name = "pysqlite-2.8.3";
1436 name = "pysqlite-2.8.3";
1450 buildInputs = with self; [];
1437 buildInputs = with self; [];
1451 doCheck = false;
1438 doCheck = false;
1452 propagatedBuildInputs = with self; [];
1439 propagatedBuildInputs = with self; [];
1453 src = fetchurl {
1440 src = fetchurl {
1454 url = "https://pypi.python.org/packages/42/02/981b6703e3c83c5b25a829c6e77aad059f9481b0bbacb47e6e8ca12bd731/pysqlite-2.8.3.tar.gz";
1441 url = "https://pypi.python.org/packages/42/02/981b6703e3c83c5b25a829c6e77aad059f9481b0bbacb47e6e8ca12bd731/pysqlite-2.8.3.tar.gz";
1455 md5 = "033f17b8644577715aee55e8832ac9fc";
1442 md5 = "033f17b8644577715aee55e8832ac9fc";
1456 };
1443 };
1457 meta = {
1444 meta = {
1458 license = [ { fullName = "zlib/libpng License"; } { fullName = "zlib/libpng license"; } ];
1445 license = [ { fullName = "zlib/libpng License"; } { fullName = "zlib/libpng license"; } ];
1459 };
1446 };
1460 };
1447 };
1461 pytest = super.buildPythonPackage {
1448 pytest = super.buildPythonPackage {
1462 name = "pytest-3.2.3";
1449 name = "pytest-3.2.3";
1463 buildInputs = with self; [];
1450 buildInputs = with self; [];
1464 doCheck = false;
1451 doCheck = false;
1465 propagatedBuildInputs = with self; [py setuptools];
1452 propagatedBuildInputs = with self; [py setuptools];
1466 src = fetchurl {
1453 src = fetchurl {
1467 url = "https://pypi.python.org/packages/53/d0/208853c09be8377e6d4de7c0df875ef7ef37189373d76a74b65b44e50528/pytest-3.2.3.tar.gz";
1454 url = "https://pypi.python.org/packages/53/d0/208853c09be8377e6d4de7c0df875ef7ef37189373d76a74b65b44e50528/pytest-3.2.3.tar.gz";
1468 md5 = "698f8929e095a1c37876b5567943be79";
1455 md5 = "698f8929e095a1c37876b5567943be79";
1469 };
1456 };
1470 meta = {
1457 meta = {
1471 license = [ pkgs.lib.licenses.mit ];
1458 license = [ pkgs.lib.licenses.mit ];
1472 };
1459 };
1473 };
1460 };
1474 pytest-catchlog = super.buildPythonPackage {
1461 pytest-catchlog = super.buildPythonPackage {
1475 name = "pytest-catchlog-1.2.2";
1462 name = "pytest-catchlog-1.2.2";
1476 buildInputs = with self; [];
1463 buildInputs = with self; [];
1477 doCheck = false;
1464 doCheck = false;
1478 propagatedBuildInputs = with self; [py pytest];
1465 propagatedBuildInputs = with self; [py pytest];
1479 src = fetchurl {
1466 src = fetchurl {
1480 url = "https://pypi.python.org/packages/f2/2b/2faccdb1a978fab9dd0bf31cca9f6847fbe9184a0bdcc3011ac41dd44191/pytest-catchlog-1.2.2.zip";
1467 url = "https://pypi.python.org/packages/f2/2b/2faccdb1a978fab9dd0bf31cca9f6847fbe9184a0bdcc3011ac41dd44191/pytest-catchlog-1.2.2.zip";
1481 md5 = "09d890c54c7456c818102b7ff8c182c8";
1468 md5 = "09d890c54c7456c818102b7ff8c182c8";
1482 };
1469 };
1483 meta = {
1470 meta = {
1484 license = [ pkgs.lib.licenses.mit ];
1471 license = [ pkgs.lib.licenses.mit ];
1485 };
1472 };
1486 };
1473 };
1487 pytest-cov = super.buildPythonPackage {
1474 pytest-cov = super.buildPythonPackage {
1488 name = "pytest-cov-2.5.1";
1475 name = "pytest-cov-2.5.1";
1489 buildInputs = with self; [];
1476 buildInputs = with self; [];
1490 doCheck = false;
1477 doCheck = false;
1491 propagatedBuildInputs = with self; [pytest coverage];
1478 propagatedBuildInputs = with self; [pytest coverage];
1492 src = fetchurl {
1479 src = fetchurl {
1493 url = "https://pypi.python.org/packages/24/b4/7290d65b2f3633db51393bdf8ae66309b37620bc3ec116c5e357e3e37238/pytest-cov-2.5.1.tar.gz";
1480 url = "https://pypi.python.org/packages/24/b4/7290d65b2f3633db51393bdf8ae66309b37620bc3ec116c5e357e3e37238/pytest-cov-2.5.1.tar.gz";
1494 md5 = "5acf38d4909e19819eb5c1754fbfc0ac";
1481 md5 = "5acf38d4909e19819eb5c1754fbfc0ac";
1495 };
1482 };
1496 meta = {
1483 meta = {
1497 license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.mit ];
1484 license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.mit ];
1498 };
1485 };
1499 };
1486 };
1500 pytest-profiling = super.buildPythonPackage {
1487 pytest-profiling = super.buildPythonPackage {
1501 name = "pytest-profiling-1.2.11";
1488 name = "pytest-profiling-1.2.11";
1502 buildInputs = with self; [];
1489 buildInputs = with self; [];
1503 doCheck = false;
1490 doCheck = false;
1504 propagatedBuildInputs = with self; [six pytest gprof2dot];
1491 propagatedBuildInputs = with self; [six pytest gprof2dot];
1505 src = fetchurl {
1492 src = fetchurl {
1506 url = "https://pypi.python.org/packages/c0/4a/b4aa786e93c07a86f1f87c581a36bf355a9e06a9da7e00dbd05047626bd2/pytest-profiling-1.2.11.tar.gz";
1493 url = "https://pypi.python.org/packages/c0/4a/b4aa786e93c07a86f1f87c581a36bf355a9e06a9da7e00dbd05047626bd2/pytest-profiling-1.2.11.tar.gz";
1507 md5 = "9ef6b60248731be5d44477980408e8f7";
1494 md5 = "9ef6b60248731be5d44477980408e8f7";
1508 };
1495 };
1509 meta = {
1496 meta = {
1510 license = [ pkgs.lib.licenses.mit ];
1497 license = [ pkgs.lib.licenses.mit ];
1511 };
1498 };
1512 };
1499 };
1513 pytest-runner = super.buildPythonPackage {
1500 pytest-runner = super.buildPythonPackage {
1514 name = "pytest-runner-2.11.1";
1501 name = "pytest-runner-2.11.1";
1515 buildInputs = with self; [];
1502 buildInputs = with self; [];
1516 doCheck = false;
1503 doCheck = false;
1517 propagatedBuildInputs = with self; [];
1504 propagatedBuildInputs = with self; [];
1518 src = fetchurl {
1505 src = fetchurl {
1519 url = "https://pypi.python.org/packages/9e/4d/08889e5e27a9f5d6096b9ad257f4dea1faabb03c5ded8f665ead448f5d8a/pytest-runner-2.11.1.tar.gz";
1506 url = "https://pypi.python.org/packages/9e/4d/08889e5e27a9f5d6096b9ad257f4dea1faabb03c5ded8f665ead448f5d8a/pytest-runner-2.11.1.tar.gz";
1520 md5 = "bdb73eb18eca2727944a2dcf963c5a81";
1507 md5 = "bdb73eb18eca2727944a2dcf963c5a81";
1521 };
1508 };
1522 meta = {
1509 meta = {
1523 license = [ pkgs.lib.licenses.mit ];
1510 license = [ pkgs.lib.licenses.mit ];
1524 };
1511 };
1525 };
1512 };
1526 pytest-sugar = super.buildPythonPackage {
1513 pytest-sugar = super.buildPythonPackage {
1527 name = "pytest-sugar-0.9.0";
1514 name = "pytest-sugar-0.9.0";
1528 buildInputs = with self; [];
1515 buildInputs = with self; [];
1529 doCheck = false;
1516 doCheck = false;
1530 propagatedBuildInputs = with self; [pytest termcolor];
1517 propagatedBuildInputs = with self; [pytest termcolor];
1531 src = fetchurl {
1518 src = fetchurl {
1532 url = "https://pypi.python.org/packages/49/d8/c5ff6cca3ce2ebd8b73eec89779bf6b4a7737456a70e8ea4d44c1ff90f71/pytest-sugar-0.9.0.tar.gz";
1519 url = "https://pypi.python.org/packages/49/d8/c5ff6cca3ce2ebd8b73eec89779bf6b4a7737456a70e8ea4d44c1ff90f71/pytest-sugar-0.9.0.tar.gz";
1533 md5 = "89fbff17277fa6a95a560a04b68cb9f9";
1520 md5 = "89fbff17277fa6a95a560a04b68cb9f9";
1534 };
1521 };
1535 meta = {
1522 meta = {
1536 license = [ pkgs.lib.licenses.bsdOriginal ];
1523 license = [ pkgs.lib.licenses.bsdOriginal ];
1537 };
1524 };
1538 };
1525 };
1539 pytest-timeout = super.buildPythonPackage {
1526 pytest-timeout = super.buildPythonPackage {
1540 name = "pytest-timeout-1.2.0";
1527 name = "pytest-timeout-1.2.0";
1541 buildInputs = with self; [];
1528 buildInputs = with self; [];
1542 doCheck = false;
1529 doCheck = false;
1543 propagatedBuildInputs = with self; [pytest];
1530 propagatedBuildInputs = with self; [pytest];
1544 src = fetchurl {
1531 src = fetchurl {
1545 url = "https://pypi.python.org/packages/cc/b7/b2a61365ea6b6d2e8881360ae7ed8dad0327ad2df89f2f0be4a02304deb2/pytest-timeout-1.2.0.tar.gz";
1532 url = "https://pypi.python.org/packages/cc/b7/b2a61365ea6b6d2e8881360ae7ed8dad0327ad2df89f2f0be4a02304deb2/pytest-timeout-1.2.0.tar.gz";
1546 md5 = "83607d91aa163562c7ee835da57d061d";
1533 md5 = "83607d91aa163562c7ee835da57d061d";
1547 };
1534 };
1548 meta = {
1535 meta = {
1549 license = [ pkgs.lib.licenses.mit { fullName = "DFSG approved"; } ];
1536 license = [ pkgs.lib.licenses.mit { fullName = "DFSG approved"; } ];
1550 };
1537 };
1551 };
1538 };
1552 python-dateutil = super.buildPythonPackage {
1539 python-dateutil = super.buildPythonPackage {
1553 name = "python-dateutil-2.1";
1540 name = "python-dateutil-2.6.1";
1554 buildInputs = with self; [];
1541 buildInputs = with self; [];
1555 doCheck = false;
1542 doCheck = false;
1556 propagatedBuildInputs = with self; [six];
1543 propagatedBuildInputs = with self; [six];
1557 src = fetchurl {
1544 src = fetchurl {
1558 url = "https://pypi.python.org/packages/65/52/9c18dac21f174ad31b65e22d24297864a954e6fe65876eba3f5773d2da43/python-dateutil-2.1.tar.gz";
1545 url = "https://pypi.python.org/packages/54/bb/f1db86504f7a49e1d9b9301531181b00a1c7325dc85a29160ee3eaa73a54/python-dateutil-2.6.1.tar.gz";
1559 md5 = "1534bb15cf311f07afaa3aacba1c028b";
1546 md5 = "db38f6b4511cefd76014745bb0cc45a4";
1560 };
1547 };
1561 meta = {
1548 meta = {
1562 license = [ { fullName = "Simplified BSD"; } ];
1549 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "Simplified BSD"; } ];
1563 };
1550 };
1564 };
1551 };
1565 python-editor = super.buildPythonPackage {
1552 python-editor = super.buildPythonPackage {
1566 name = "python-editor-1.0.3";
1553 name = "python-editor-1.0.3";
1567 buildInputs = with self; [];
1554 buildInputs = with self; [];
1568 doCheck = false;
1555 doCheck = false;
1569 propagatedBuildInputs = with self; [];
1556 propagatedBuildInputs = with self; [];
1570 src = fetchurl {
1557 src = fetchurl {
1571 url = "https://pypi.python.org/packages/65/1e/adf6e000ea5dc909aa420352d6ba37f16434c8a3c2fa030445411a1ed545/python-editor-1.0.3.tar.gz";
1558 url = "https://pypi.python.org/packages/65/1e/adf6e000ea5dc909aa420352d6ba37f16434c8a3c2fa030445411a1ed545/python-editor-1.0.3.tar.gz";
1572 md5 = "0aca5f2ef176ce68e98a5b7e31372835";
1559 md5 = "0aca5f2ef176ce68e98a5b7e31372835";
1573 };
1560 };
1574 meta = {
1561 meta = {
1575 license = [ pkgs.lib.licenses.asl20 { fullName = "Apache"; } ];
1562 license = [ pkgs.lib.licenses.asl20 { fullName = "Apache"; } ];
1576 };
1563 };
1577 };
1564 };
1578 python-ldap = super.buildPythonPackage {
1565 python-ldap = super.buildPythonPackage {
1579 name = "python-ldap-2.4.45";
1566 name = "python-ldap-2.4.45";
1580 buildInputs = with self; [];
1567 buildInputs = with self; [];
1581 doCheck = false;
1568 doCheck = false;
1582 propagatedBuildInputs = with self; [setuptools];
1569 propagatedBuildInputs = with self; [setuptools];
1583 src = fetchurl {
1570 src = fetchurl {
1584 url = "https://pypi.python.org/packages/ce/52/6b5372d0166820f4a4b0a88ed73dc7504219355049fc1d266d8ccdb7942e/python-ldap-2.4.45.tar.gz";
1571 url = "https://pypi.python.org/packages/ce/52/6b5372d0166820f4a4b0a88ed73dc7504219355049fc1d266d8ccdb7942e/python-ldap-2.4.45.tar.gz";
1585 md5 = "6108e189a44eea8bc7d1cc281c222978";
1572 md5 = "6108e189a44eea8bc7d1cc281c222978";
1586 };
1573 };
1587 meta = {
1574 meta = {
1588 license = [ pkgs.lib.licenses.psfl ];
1575 license = [ pkgs.lib.licenses.psfl ];
1589 };
1576 };
1590 };
1577 };
1591 python-memcached = super.buildPythonPackage {
1578 python-memcached = super.buildPythonPackage {
1592 name = "python-memcached-1.58";
1579 name = "python-memcached-1.58";
1593 buildInputs = with self; [];
1580 buildInputs = with self; [];
1594 doCheck = false;
1581 doCheck = false;
1595 propagatedBuildInputs = with self; [six];
1582 propagatedBuildInputs = with self; [six];
1596 src = fetchurl {
1583 src = fetchurl {
1597 url = "https://pypi.python.org/packages/f7/62/14b2448cfb04427366f24104c9da97cf8ea380d7258a3233f066a951a8d8/python-memcached-1.58.tar.gz";
1584 url = "https://pypi.python.org/packages/f7/62/14b2448cfb04427366f24104c9da97cf8ea380d7258a3233f066a951a8d8/python-memcached-1.58.tar.gz";
1598 md5 = "23b258105013d14d899828d334e6b044";
1585 md5 = "23b258105013d14d899828d334e6b044";
1599 };
1586 };
1600 meta = {
1587 meta = {
1601 license = [ pkgs.lib.licenses.psfl ];
1588 license = [ pkgs.lib.licenses.psfl ];
1602 };
1589 };
1603 };
1590 };
1604 python-pam = super.buildPythonPackage {
1591 python-pam = super.buildPythonPackage {
1605 name = "python-pam-1.8.2";
1592 name = "python-pam-1.8.2";
1606 buildInputs = with self; [];
1593 buildInputs = with self; [];
1607 doCheck = false;
1594 doCheck = false;
1608 propagatedBuildInputs = with self; [];
1595 propagatedBuildInputs = with self; [];
1609 src = fetchurl {
1596 src = fetchurl {
1610 url = "https://pypi.python.org/packages/de/8c/f8f5d38b4f26893af267ea0b39023d4951705ab0413a39e0cf7cf4900505/python-pam-1.8.2.tar.gz";
1597 url = "https://pypi.python.org/packages/de/8c/f8f5d38b4f26893af267ea0b39023d4951705ab0413a39e0cf7cf4900505/python-pam-1.8.2.tar.gz";
1611 md5 = "db71b6b999246fb05d78ecfbe166629d";
1598 md5 = "db71b6b999246fb05d78ecfbe166629d";
1612 };
1599 };
1613 meta = {
1600 meta = {
1614 license = [ { fullName = "License :: OSI Approved :: MIT License"; } pkgs.lib.licenses.mit ];
1601 license = [ { fullName = "License :: OSI Approved :: MIT License"; } pkgs.lib.licenses.mit ];
1615 };
1602 };
1616 };
1603 };
1617 pytz = super.buildPythonPackage {
1604 pytz = super.buildPythonPackage {
1618 name = "pytz-2015.4";
1605 name = "pytz-2017.3";
1619 buildInputs = with self; [];
1606 buildInputs = with self; [];
1620 doCheck = false;
1607 doCheck = false;
1621 propagatedBuildInputs = with self; [];
1608 propagatedBuildInputs = with self; [];
1622 src = fetchurl {
1609 src = fetchurl {
1623 url = "https://pypi.python.org/packages/7e/1a/f43b5c92df7b156822030fed151327ea096bcf417e45acc23bd1df43472f/pytz-2015.4.zip";
1610 url = "https://pypi.python.org/packages/60/88/d3152c234da4b2a1f7a989f89609ea488225eaea015bc16fbde2b3fdfefa/pytz-2017.3.zip";
1624 md5 = "233f2a2b370d03f9b5911700cc9ebf3c";
1611 md5 = "7006b56c0d68a162d9fe57d4249c3171";
1625 };
1612 };
1626 meta = {
1613 meta = {
1627 license = [ pkgs.lib.licenses.mit ];
1614 license = [ pkgs.lib.licenses.mit ];
1628 };
1615 };
1629 };
1616 };
1630 pyzmq = super.buildPythonPackage {
1617 pyzmq = super.buildPythonPackage {
1631 name = "pyzmq-14.6.0";
1618 name = "pyzmq-14.6.0";
1632 buildInputs = with self; [];
1619 buildInputs = with self; [];
1633 doCheck = false;
1620 doCheck = false;
1634 propagatedBuildInputs = with self; [];
1621 propagatedBuildInputs = with self; [];
1635 src = fetchurl {
1622 src = fetchurl {
1636 url = "https://pypi.python.org/packages/8a/3b/5463d5a9d712cd8bbdac335daece0d69f6a6792da4e3dd89956c0db4e4e6/pyzmq-14.6.0.tar.gz";
1623 url = "https://pypi.python.org/packages/8a/3b/5463d5a9d712cd8bbdac335daece0d69f6a6792da4e3dd89956c0db4e4e6/pyzmq-14.6.0.tar.gz";
1637 md5 = "395b5de95a931afa5b14c9349a5b8024";
1624 md5 = "395b5de95a931afa5b14c9349a5b8024";
1638 };
1625 };
1639 meta = {
1626 meta = {
1640 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "LGPL+BSD"; } { fullName = "GNU Library or Lesser General Public License (LGPL)"; } ];
1627 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "LGPL+BSD"; } { fullName = "GNU Library or Lesser General Public License (LGPL)"; } ];
1641 };
1628 };
1642 };
1629 };
1643 recaptcha-client = super.buildPythonPackage {
1630 recaptcha-client = super.buildPythonPackage {
1644 name = "recaptcha-client-1.0.6";
1631 name = "recaptcha-client-1.0.6";
1645 buildInputs = with self; [];
1632 buildInputs = with self; [];
1646 doCheck = false;
1633 doCheck = false;
1647 propagatedBuildInputs = with self; [];
1634 propagatedBuildInputs = with self; [];
1648 src = fetchurl {
1635 src = fetchurl {
1649 url = "https://pypi.python.org/packages/0a/ea/5f2fbbfd894bdac1c68ef8d92019066cfcf9fbff5fe3d728d2b5c25c8db4/recaptcha-client-1.0.6.tar.gz";
1636 url = "https://pypi.python.org/packages/0a/ea/5f2fbbfd894bdac1c68ef8d92019066cfcf9fbff5fe3d728d2b5c25c8db4/recaptcha-client-1.0.6.tar.gz";
1650 md5 = "74228180f7e1fb76c4d7089160b0d919";
1637 md5 = "74228180f7e1fb76c4d7089160b0d919";
1651 };
1638 };
1652 meta = {
1639 meta = {
1653 license = [ { fullName = "MIT/X11"; } ];
1640 license = [ { fullName = "MIT/X11"; } ];
1654 };
1641 };
1655 };
1642 };
1656 redis = super.buildPythonPackage {
1643 redis = super.buildPythonPackage {
1657 name = "redis-2.10.6";
1644 name = "redis-2.10.6";
1658 buildInputs = with self; [];
1645 buildInputs = with self; [];
1659 doCheck = false;
1646 doCheck = false;
1660 propagatedBuildInputs = with self; [];
1647 propagatedBuildInputs = with self; [];
1661 src = fetchurl {
1648 src = fetchurl {
1662 url = "https://pypi.python.org/packages/09/8d/6d34b75326bf96d4139a2ddd8e74b80840f800a0a79f9294399e212cb9a7/redis-2.10.6.tar.gz";
1649 url = "https://pypi.python.org/packages/09/8d/6d34b75326bf96d4139a2ddd8e74b80840f800a0a79f9294399e212cb9a7/redis-2.10.6.tar.gz";
1663 md5 = "048348d8cfe0b5d0bba2f4d835005c3b";
1650 md5 = "048348d8cfe0b5d0bba2f4d835005c3b";
1664 };
1651 };
1665 meta = {
1652 meta = {
1666 license = [ pkgs.lib.licenses.mit ];
1653 license = [ pkgs.lib.licenses.mit ];
1667 };
1654 };
1668 };
1655 };
1669 repoze.lru = super.buildPythonPackage {
1656 repoze.lru = super.buildPythonPackage {
1670 name = "repoze.lru-0.7";
1657 name = "repoze.lru-0.7";
1671 buildInputs = with self; [];
1658 buildInputs = with self; [];
1672 doCheck = false;
1659 doCheck = false;
1673 propagatedBuildInputs = with self; [];
1660 propagatedBuildInputs = with self; [];
1674 src = fetchurl {
1661 src = fetchurl {
1675 url = "https://pypi.python.org/packages/12/bc/595a77c4b5e204847fdf19268314ef59c85193a9dc9f83630fc459c0fee5/repoze.lru-0.7.tar.gz";
1662 url = "https://pypi.python.org/packages/12/bc/595a77c4b5e204847fdf19268314ef59c85193a9dc9f83630fc459c0fee5/repoze.lru-0.7.tar.gz";
1676 md5 = "c08cc030387e0b1fc53c5c7d964b35e2";
1663 md5 = "c08cc030387e0b1fc53c5c7d964b35e2";
1677 };
1664 };
1678 meta = {
1665 meta = {
1679 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1666 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1680 };
1667 };
1681 };
1668 };
1682 requests = super.buildPythonPackage {
1669 requests = super.buildPythonPackage {
1683 name = "requests-2.9.1";
1670 name = "requests-2.9.1";
1684 buildInputs = with self; [];
1671 buildInputs = with self; [];
1685 doCheck = false;
1672 doCheck = false;
1686 propagatedBuildInputs = with self; [];
1673 propagatedBuildInputs = with self; [];
1687 src = fetchurl {
1674 src = fetchurl {
1688 url = "https://pypi.python.org/packages/f9/6d/07c44fb1ebe04d069459a189e7dab9e4abfe9432adcd4477367c25332748/requests-2.9.1.tar.gz";
1675 url = "https://pypi.python.org/packages/f9/6d/07c44fb1ebe04d069459a189e7dab9e4abfe9432adcd4477367c25332748/requests-2.9.1.tar.gz";
1689 md5 = "0b7f480d19012ec52bab78292efd976d";
1676 md5 = "0b7f480d19012ec52bab78292efd976d";
1690 };
1677 };
1691 meta = {
1678 meta = {
1692 license = [ pkgs.lib.licenses.asl20 ];
1679 license = [ pkgs.lib.licenses.asl20 ];
1693 };
1680 };
1694 };
1681 };
1695 rhodecode-enterprise-ce = super.buildPythonPackage {
1682 rhodecode-enterprise-ce = super.buildPythonPackage {
1696 name = "rhodecode-enterprise-ce-4.11.0";
1683 name = "rhodecode-enterprise-ce-4.11.0";
1697 buildInputs = with self; [pytest py pytest-cov pytest-sugar pytest-runner pytest-catchlog pytest-profiling gprof2dot pytest-timeout mock WebTest cov-core coverage configobj];
1684 buildInputs = with self; [pytest py pytest-cov pytest-sugar pytest-runner pytest-catchlog pytest-profiling gprof2dot pytest-timeout mock WebTest cov-core coverage configobj];
1698 doCheck = true;
1685 doCheck = true;
1699 propagatedBuildInputs = with self; [Babel Beaker FormEncode Mako Markdown MarkupSafe MySQL-python Paste PasteDeploy PasteScript Pygments pygments-markdown-lexer Pylons Routes SQLAlchemy Tempita URLObject WebError WebHelpers WebHelpers2 WebOb WebTest Whoosh alembic amqplib anyjson appenlight-client authomatic cssselect celery channelstream colander decorator deform docutils gevent gunicorn infrae.cache ipython iso8601 kombu lxml msgpack-python nbconvert packaging psycopg2 py-gfm pycrypto pycurl pyparsing pyramid pyramid-debugtoolbar pyramid-mako pyramid-beaker pysqlite python-dateutil python-ldap python-memcached python-pam recaptcha-client redis repoze.lru requests simplejson sshpubkeys subprocess32 waitress zope.cachedescriptors dogpile.cache dogpile.core psutil py-bcrypt];
1686 propagatedBuildInputs = with self; [setuptools-scm amqplib amqp authomatic Babel Beaker celery Chameleon channelstream click colander configobj cssselect decorator deform docutils dogpile.cache dogpile.core ecdsa FormEncode future futures gnureadline infrae.cache iso8601 itsdangerous Jinja2 billiard kombu lxml Mako Markdown MarkupSafe msgpack-python MySQL-python objgraph packaging Paste PasteDeploy PasteScript pathlib2 peppercorn psutil psycopg2 py-bcrypt pycrypto pycurl pyflakes pygments-markdown-lexer Pygments pyparsing pyramid-beaker pyramid-debugtoolbar pyramid-jinja2 pyramid-mako pyramid pysqlite python-dateutil python-ldap python-memcached python-pam pytz pyzmq py-gfm recaptcha-client redis repoze.lru requests Routes setproctitle simplejson six SQLAlchemy sshpubkeys subprocess32 Tempita translationstring trollius urllib3 URLObject venusian WebError WebHelpers2 WebHelpers WebOb Whoosh wsgiref zope.cachedescriptors zope.deprecation zope.event zope.interface nbconvert bleach nbformat jupyter-client alembic invoke bumpversion transifex-client gevent greenlet gunicorn waitress uWSGI ipdb ipython CProfileV bottle rhodecode-tools appenlight-client pytest py pytest-cov pytest-sugar pytest-runner pytest-catchlog pytest-profiling gprof2dot pytest-timeout mock WebTest cov-core coverage];
1700 src = ./.;
1687 src = ./.;
1701 meta = {
1688 meta = {
1702 license = [ { fullName = "Affero GNU General Public License v3 or later (AGPLv3+)"; } { fullName = "AGPLv3, and Commercial License"; } ];
1689 license = [ { fullName = "Affero GNU General Public License v3 or later (AGPLv3+)"; } { fullName = "AGPLv3, and Commercial License"; } ];
1703 };
1690 };
1704 };
1691 };
1705 rhodecode-tools = super.buildPythonPackage {
1692 rhodecode-tools = super.buildPythonPackage {
1706 name = "rhodecode-tools-0.14.0";
1693 name = "rhodecode-tools-0.14.0";
1707 buildInputs = with self; [];
1694 buildInputs = with self; [];
1708 doCheck = false;
1695 doCheck = false;
1709 propagatedBuildInputs = with self; [click future six Mako MarkupSafe requests elasticsearch elasticsearch-dsl urllib3 Whoosh];
1696 propagatedBuildInputs = with self; [click future six Mako MarkupSafe requests elasticsearch elasticsearch-dsl urllib3 Whoosh];
1710 src = fetchurl {
1697 src = fetchurl {
1711 url = "https://code.rhodecode.com/rhodecode-tools-ce/archive/v0.14.0.tar.gz?md5=15de9be3d185d832c4af2156fefc8eeb";
1698 url = "https://code.rhodecode.com/rhodecode-tools-ce/archive/v0.14.0.tar.gz?md5=15de9be3d185d832c4af2156fefc8eeb";
1712 md5 = "15de9be3d185d832c4af2156fefc8eeb";
1699 md5 = "15de9be3d185d832c4af2156fefc8eeb";
1713 };
1700 };
1714 meta = {
1701 meta = {
1715 license = [ { fullName = "AGPLv3 and Proprietary"; } ];
1702 license = [ { fullName = "AGPLv3 and Proprietary"; } ];
1716 };
1703 };
1717 };
1704 };
1718 scandir = super.buildPythonPackage {
1705 scandir = super.buildPythonPackage {
1719 name = "scandir-1.6";
1706 name = "scandir-1.6";
1720 buildInputs = with self; [];
1707 buildInputs = with self; [];
1721 doCheck = false;
1708 doCheck = false;
1722 propagatedBuildInputs = with self; [];
1709 propagatedBuildInputs = with self; [];
1723 src = fetchurl {
1710 src = fetchurl {
1724 url = "https://pypi.python.org/packages/77/3f/916f524f50ee65e3f465a280d2851bd63685250fddb3020c212b3977664d/scandir-1.6.tar.gz";
1711 url = "https://pypi.python.org/packages/77/3f/916f524f50ee65e3f465a280d2851bd63685250fddb3020c212b3977664d/scandir-1.6.tar.gz";
1725 md5 = "0180ddb97c96cbb2d4f25d2ae11c64ac";
1712 md5 = "0180ddb97c96cbb2d4f25d2ae11c64ac";
1726 };
1713 };
1727 meta = {
1714 meta = {
1728 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "New BSD License"; } ];
1715 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "New BSD License"; } ];
1729 };
1716 };
1730 };
1717 };
1731 setproctitle = super.buildPythonPackage {
1718 setproctitle = super.buildPythonPackage {
1732 name = "setproctitle-1.1.10";
1719 name = "setproctitle-1.1.10";
1733 buildInputs = with self; [];
1720 buildInputs = with self; [];
1734 doCheck = false;
1721 doCheck = false;
1735 propagatedBuildInputs = with self; [];
1722 propagatedBuildInputs = with self; [];
1736 src = fetchurl {
1723 src = fetchurl {
1737 url = "https://pypi.python.org/packages/5a/0d/dc0d2234aacba6cf1a729964383e3452c52096dc695581248b548786f2b3/setproctitle-1.1.10.tar.gz";
1724 url = "https://pypi.python.org/packages/5a/0d/dc0d2234aacba6cf1a729964383e3452c52096dc695581248b548786f2b3/setproctitle-1.1.10.tar.gz";
1738 md5 = "2dcdd1b761700a5a13252fea3dfd1977";
1725 md5 = "2dcdd1b761700a5a13252fea3dfd1977";
1739 };
1726 };
1740 meta = {
1727 meta = {
1741 license = [ pkgs.lib.licenses.bsdOriginal ];
1728 license = [ pkgs.lib.licenses.bsdOriginal ];
1742 };
1729 };
1743 };
1730 };
1744 setuptools = super.buildPythonPackage {
1731 setuptools = super.buildPythonPackage {
1745 name = "setuptools-30.1.0";
1732 name = "setuptools-30.1.0";
1746 buildInputs = with self; [];
1733 buildInputs = with self; [];
1747 doCheck = false;
1734 doCheck = false;
1748 propagatedBuildInputs = with self; [];
1735 propagatedBuildInputs = with self; [];
1749 src = fetchurl {
1736 src = fetchurl {
1750 url = "https://pypi.python.org/packages/1e/43/002c8616db9a3e7be23c2556e39b90a32bb40ba0dc652de1999d5334d372/setuptools-30.1.0.tar.gz";
1737 url = "https://pypi.python.org/packages/1e/43/002c8616db9a3e7be23c2556e39b90a32bb40ba0dc652de1999d5334d372/setuptools-30.1.0.tar.gz";
1751 md5 = "cac497f42e5096ac8df29e38d3f81c3e";
1738 md5 = "cac497f42e5096ac8df29e38d3f81c3e";
1752 };
1739 };
1753 meta = {
1740 meta = {
1754 license = [ pkgs.lib.licenses.mit ];
1741 license = [ pkgs.lib.licenses.mit ];
1755 };
1742 };
1756 };
1743 };
1757 setuptools-scm = super.buildPythonPackage {
1744 setuptools-scm = super.buildPythonPackage {
1758 name = "setuptools-scm-1.15.0";
1745 name = "setuptools-scm-1.15.6";
1759 buildInputs = with self; [];
1746 buildInputs = with self; [];
1760 doCheck = false;
1747 doCheck = false;
1761 propagatedBuildInputs = with self; [];
1748 propagatedBuildInputs = with self; [];
1762 src = fetchurl {
1749 src = fetchurl {
1763 url = "https://pypi.python.org/packages/80/b7/31b6ae5fcb188e37f7e31abe75f9be90490a5456a72860fa6e643f8a3cbc/setuptools_scm-1.15.0.tar.gz";
1750 url = "https://pypi.python.org/packages/03/6d/aafdd01edd227ee879b691455bf19895091872af7e48192bea1758c82032/setuptools_scm-1.15.6.tar.gz";
1764 md5 = "b6916c78ed6253d6602444fad4279c5b";
1751 md5 = "f17493d53f0d842bb0152f214775640b";
1765 };
1752 };
1766 meta = {
1753 meta = {
1767 license = [ pkgs.lib.licenses.mit ];
1754 license = [ pkgs.lib.licenses.mit ];
1768 };
1755 };
1769 };
1756 };
1770 simplegeneric = super.buildPythonPackage {
1757 simplegeneric = super.buildPythonPackage {
1771 name = "simplegeneric-0.8.1";
1758 name = "simplegeneric-0.8.1";
1772 buildInputs = with self; [];
1759 buildInputs = with self; [];
1773 doCheck = false;
1760 doCheck = false;
1774 propagatedBuildInputs = with self; [];
1761 propagatedBuildInputs = with self; [];
1775 src = fetchurl {
1762 src = fetchurl {
1776 url = "https://pypi.python.org/packages/3d/57/4d9c9e3ae9a255cd4e1106bb57e24056d3d0709fc01b2e3e345898e49d5b/simplegeneric-0.8.1.zip";
1763 url = "https://pypi.python.org/packages/3d/57/4d9c9e3ae9a255cd4e1106bb57e24056d3d0709fc01b2e3e345898e49d5b/simplegeneric-0.8.1.zip";
1777 md5 = "f9c1fab00fd981be588fc32759f474e3";
1764 md5 = "f9c1fab00fd981be588fc32759f474e3";
1778 };
1765 };
1779 meta = {
1766 meta = {
1780 license = [ pkgs.lib.licenses.zpt21 ];
1767 license = [ pkgs.lib.licenses.zpt21 ];
1781 };
1768 };
1782 };
1769 };
1783 simplejson = super.buildPythonPackage {
1770 simplejson = super.buildPythonPackage {
1784 name = "simplejson-3.11.1";
1771 name = "simplejson-3.11.1";
1785 buildInputs = with self; [];
1772 buildInputs = with self; [];
1786 doCheck = false;
1773 doCheck = false;
1787 propagatedBuildInputs = with self; [];
1774 propagatedBuildInputs = with self; [];
1788 src = fetchurl {
1775 src = fetchurl {
1789 url = "https://pypi.python.org/packages/08/48/c97b668d6da7d7bebe7ea1817a6f76394b0ec959cb04214ca833c34359df/simplejson-3.11.1.tar.gz";
1776 url = "https://pypi.python.org/packages/08/48/c97b668d6da7d7bebe7ea1817a6f76394b0ec959cb04214ca833c34359df/simplejson-3.11.1.tar.gz";
1790 md5 = "6e2f1bd5fb0a926facf5d89d217a7183";
1777 md5 = "6e2f1bd5fb0a926facf5d89d217a7183";
1791 };
1778 };
1792 meta = {
1779 meta = {
1793 license = [ { fullName = "Academic Free License (AFL)"; } pkgs.lib.licenses.mit ];
1780 license = [ { fullName = "Academic Free License (AFL)"; } pkgs.lib.licenses.mit ];
1794 };
1781 };
1795 };
1782 };
1796 six = super.buildPythonPackage {
1783 six = super.buildPythonPackage {
1797 name = "six-1.11.0";
1784 name = "six-1.11.0";
1798 buildInputs = with self; [];
1785 buildInputs = with self; [];
1799 doCheck = false;
1786 doCheck = false;
1800 propagatedBuildInputs = with self; [];
1787 propagatedBuildInputs = with self; [];
1801 src = fetchurl {
1788 src = fetchurl {
1802 url = "https://pypi.python.org/packages/16/d8/bc6316cf98419719bd59c91742194c111b6f2e85abac88e496adefaf7afe/six-1.11.0.tar.gz";
1789 url = "https://pypi.python.org/packages/16/d8/bc6316cf98419719bd59c91742194c111b6f2e85abac88e496adefaf7afe/six-1.11.0.tar.gz";
1803 md5 = "d12789f9baf7e9fb2524c0c64f1773f8";
1790 md5 = "d12789f9baf7e9fb2524c0c64f1773f8";
1804 };
1791 };
1805 meta = {
1792 meta = {
1806 license = [ pkgs.lib.licenses.mit ];
1793 license = [ pkgs.lib.licenses.mit ];
1807 };
1794 };
1808 };
1795 };
1809 sshpubkeys = super.buildPythonPackage {
1796 sshpubkeys = super.buildPythonPackage {
1810 name = "sshpubkeys-2.2.0";
1797 name = "sshpubkeys-2.2.0";
1811 buildInputs = with self; [];
1798 buildInputs = with self; [];
1812 doCheck = false;
1799 doCheck = false;
1813 propagatedBuildInputs = with self; [pycrypto ecdsa];
1800 propagatedBuildInputs = with self; [pycrypto ecdsa];
1814 src = fetchurl {
1801 src = fetchurl {
1815 url = "https://pypi.python.org/packages/27/da/337fabeb3dca6b62039a93ceaa636f25065e0ae92b575b1235342076cf0a/sshpubkeys-2.2.0.tar.gz";
1802 url = "https://pypi.python.org/packages/27/da/337fabeb3dca6b62039a93ceaa636f25065e0ae92b575b1235342076cf0a/sshpubkeys-2.2.0.tar.gz";
1816 md5 = "458e45f6b92b1afa84f0ffe1f1c90935";
1803 md5 = "458e45f6b92b1afa84f0ffe1f1c90935";
1817 };
1804 };
1818 meta = {
1805 meta = {
1819 license = [ pkgs.lib.licenses.bsdOriginal ];
1806 license = [ pkgs.lib.licenses.bsdOriginal ];
1820 };
1807 };
1821 };
1808 };
1822 subprocess32 = super.buildPythonPackage {
1809 subprocess32 = super.buildPythonPackage {
1823 name = "subprocess32-3.2.7";
1810 name = "subprocess32-3.2.7";
1824 buildInputs = with self; [];
1811 buildInputs = with self; [];
1825 doCheck = false;
1812 doCheck = false;
1826 propagatedBuildInputs = with self; [];
1813 propagatedBuildInputs = with self; [];
1827 src = fetchurl {
1814 src = fetchurl {
1828 url = "https://pypi.python.org/packages/b8/2f/49e53b0d0e94611a2dc624a1ad24d41b6d94d0f1b0a078443407ea2214c2/subprocess32-3.2.7.tar.gz";
1815 url = "https://pypi.python.org/packages/b8/2f/49e53b0d0e94611a2dc624a1ad24d41b6d94d0f1b0a078443407ea2214c2/subprocess32-3.2.7.tar.gz";
1829 md5 = "824c801e479d3e916879aae3e9c15e16";
1816 md5 = "824c801e479d3e916879aae3e9c15e16";
1830 };
1817 };
1831 meta = {
1818 meta = {
1832 license = [ pkgs.lib.licenses.psfl ];
1819 license = [ pkgs.lib.licenses.psfl ];
1833 };
1820 };
1834 };
1821 };
1835 termcolor = super.buildPythonPackage {
1822 termcolor = super.buildPythonPackage {
1836 name = "termcolor-1.1.0";
1823 name = "termcolor-1.1.0";
1837 buildInputs = with self; [];
1824 buildInputs = with self; [];
1838 doCheck = false;
1825 doCheck = false;
1839 propagatedBuildInputs = with self; [];
1826 propagatedBuildInputs = with self; [];
1840 src = fetchurl {
1827 src = fetchurl {
1841 url = "https://pypi.python.org/packages/8a/48/a76be51647d0eb9f10e2a4511bf3ffb8cc1e6b14e9e4fab46173aa79f981/termcolor-1.1.0.tar.gz";
1828 url = "https://pypi.python.org/packages/8a/48/a76be51647d0eb9f10e2a4511bf3ffb8cc1e6b14e9e4fab46173aa79f981/termcolor-1.1.0.tar.gz";
1842 md5 = "043e89644f8909d462fbbfa511c768df";
1829 md5 = "043e89644f8909d462fbbfa511c768df";
1843 };
1830 };
1844 meta = {
1831 meta = {
1845 license = [ pkgs.lib.licenses.mit ];
1832 license = [ pkgs.lib.licenses.mit ];
1846 };
1833 };
1847 };
1834 };
1848 testpath = super.buildPythonPackage {
1835 testpath = super.buildPythonPackage {
1849 name = "testpath-0.3.1";
1836 name = "testpath-0.3.1";
1850 buildInputs = with self; [];
1837 buildInputs = with self; [];
1851 doCheck = false;
1838 doCheck = false;
1852 propagatedBuildInputs = with self; [];
1839 propagatedBuildInputs = with self; [];
1853 src = fetchurl {
1840 src = fetchurl {
1854 url = "https://pypi.python.org/packages/f4/8b/b71e9ee10e5f751e9d959bc750ab122ba04187f5aa52aabdc4e63b0e31a7/testpath-0.3.1.tar.gz";
1841 url = "https://pypi.python.org/packages/f4/8b/b71e9ee10e5f751e9d959bc750ab122ba04187f5aa52aabdc4e63b0e31a7/testpath-0.3.1.tar.gz";
1855 md5 = "2cd5ed5522fda781bb497c9d80ae2fc9";
1842 md5 = "2cd5ed5522fda781bb497c9d80ae2fc9";
1856 };
1843 };
1857 meta = {
1844 meta = {
1858 license = [ pkgs.lib.licenses.mit ];
1845 license = [ pkgs.lib.licenses.mit ];
1859 };
1846 };
1860 };
1847 };
1861 traitlets = super.buildPythonPackage {
1848 traitlets = super.buildPythonPackage {
1862 name = "traitlets-4.3.2";
1849 name = "traitlets-4.3.2";
1863 buildInputs = with self; [];
1850 buildInputs = with self; [];
1864 doCheck = false;
1851 doCheck = false;
1865 propagatedBuildInputs = with self; [ipython-genutils six decorator enum34];
1852 propagatedBuildInputs = with self; [ipython-genutils six decorator enum34];
1866 src = fetchurl {
1853 src = fetchurl {
1867 url = "https://pypi.python.org/packages/a5/98/7f5ef2fe9e9e071813aaf9cb91d1a732e0a68b6c44a32b38cb8e14c3f069/traitlets-4.3.2.tar.gz";
1854 url = "https://pypi.python.org/packages/a5/98/7f5ef2fe9e9e071813aaf9cb91d1a732e0a68b6c44a32b38cb8e14c3f069/traitlets-4.3.2.tar.gz";
1868 md5 = "3068663f2f38fd939a9eb3a500ccc154";
1855 md5 = "3068663f2f38fd939a9eb3a500ccc154";
1869 };
1856 };
1870 meta = {
1857 meta = {
1871 license = [ pkgs.lib.licenses.bsdOriginal ];
1858 license = [ pkgs.lib.licenses.bsdOriginal ];
1872 };
1859 };
1873 };
1860 };
1874 transifex-client = super.buildPythonPackage {
1861 transifex-client = super.buildPythonPackage {
1875 name = "transifex-client-0.12.5";
1862 name = "transifex-client-0.12.5";
1876 buildInputs = with self; [];
1863 buildInputs = with self; [];
1877 doCheck = false;
1864 doCheck = false;
1878 propagatedBuildInputs = with self; [urllib3 six];
1865 propagatedBuildInputs = with self; [urllib3 six];
1879 src = fetchurl {
1866 src = fetchurl {
1880 url = "https://pypi.python.org/packages/7b/86/60f31a0c9b8d0b1266ce15b6c80b55f88522140c8acfc395d5aec5e23475/transifex-client-0.12.5.tar.gz";
1867 url = "https://pypi.python.org/packages/7b/86/60f31a0c9b8d0b1266ce15b6c80b55f88522140c8acfc395d5aec5e23475/transifex-client-0.12.5.tar.gz";
1881 md5 = "e6e278117b23f60702c06e203b7e51ae";
1868 md5 = "e6e278117b23f60702c06e203b7e51ae";
1882 };
1869 };
1883 meta = {
1870 meta = {
1884 license = [ pkgs.lib.licenses.gpl2 ];
1871 license = [ pkgs.lib.licenses.gpl2 ];
1885 };
1872 };
1886 };
1873 };
1887 translationstring = super.buildPythonPackage {
1874 translationstring = super.buildPythonPackage {
1888 name = "translationstring-1.3";
1875 name = "translationstring-1.3";
1889 buildInputs = with self; [];
1876 buildInputs = with self; [];
1890 doCheck = false;
1877 doCheck = false;
1891 propagatedBuildInputs = with self; [];
1878 propagatedBuildInputs = with self; [];
1892 src = fetchurl {
1879 src = fetchurl {
1893 url = "https://pypi.python.org/packages/5e/eb/bee578cc150b44c653b63f5ebe258b5d0d812ddac12497e5f80fcad5d0b4/translationstring-1.3.tar.gz";
1880 url = "https://pypi.python.org/packages/5e/eb/bee578cc150b44c653b63f5ebe258b5d0d812ddac12497e5f80fcad5d0b4/translationstring-1.3.tar.gz";
1894 md5 = "a4b62e0f3c189c783a1685b3027f7c90";
1881 md5 = "a4b62e0f3c189c783a1685b3027f7c90";
1895 };
1882 };
1896 meta = {
1883 meta = {
1897 license = [ { fullName = "BSD-like (http://repoze.org/license.html)"; } ];
1884 license = [ { fullName = "BSD-like (http://repoze.org/license.html)"; } ];
1898 };
1885 };
1899 };
1886 };
1900 trollius = super.buildPythonPackage {
1887 trollius = super.buildPythonPackage {
1901 name = "trollius-1.0.4";
1888 name = "trollius-1.0.4";
1902 buildInputs = with self; [];
1889 buildInputs = with self; [];
1903 doCheck = false;
1890 doCheck = false;
1904 propagatedBuildInputs = with self; [futures];
1891 propagatedBuildInputs = with self; [futures];
1905 src = fetchurl {
1892 src = fetchurl {
1906 url = "https://pypi.python.org/packages/aa/e6/4141db437f55e6ee7a3fb69663239e3fde7841a811b4bef293145ad6c836/trollius-1.0.4.tar.gz";
1893 url = "https://pypi.python.org/packages/aa/e6/4141db437f55e6ee7a3fb69663239e3fde7841a811b4bef293145ad6c836/trollius-1.0.4.tar.gz";
1907 md5 = "3631a464d49d0cbfd30ab2918ef2b783";
1894 md5 = "3631a464d49d0cbfd30ab2918ef2b783";
1908 };
1895 };
1909 meta = {
1896 meta = {
1910 license = [ pkgs.lib.licenses.asl20 ];
1897 license = [ pkgs.lib.licenses.asl20 ];
1911 };
1898 };
1912 };
1899 };
1913 uWSGI = super.buildPythonPackage {
1900 uWSGI = super.buildPythonPackage {
1914 name = "uWSGI-2.0.15";
1901 name = "uWSGI-2.0.15";
1915 buildInputs = with self; [];
1902 buildInputs = with self; [];
1916 doCheck = false;
1903 doCheck = false;
1917 propagatedBuildInputs = with self; [];
1904 propagatedBuildInputs = with self; [];
1918 src = fetchurl {
1905 src = fetchurl {
1919 url = "https://pypi.python.org/packages/bb/0a/45e5aa80dc135889594bb371c082d20fb7ee7303b174874c996888cc8511/uwsgi-2.0.15.tar.gz";
1906 url = "https://pypi.python.org/packages/bb/0a/45e5aa80dc135889594bb371c082d20fb7ee7303b174874c996888cc8511/uwsgi-2.0.15.tar.gz";
1920 md5 = "fc50bd9e83b7602fa474b032167010a7";
1907 md5 = "fc50bd9e83b7602fa474b032167010a7";
1921 };
1908 };
1922 meta = {
1909 meta = {
1923 license = [ pkgs.lib.licenses.gpl2 ];
1910 license = [ pkgs.lib.licenses.gpl2 ];
1924 };
1911 };
1925 };
1912 };
1926 urllib3 = super.buildPythonPackage {
1913 urllib3 = super.buildPythonPackage {
1927 name = "urllib3-1.16";
1914 name = "urllib3-1.16";
1928 buildInputs = with self; [];
1915 buildInputs = with self; [];
1929 doCheck = false;
1916 doCheck = false;
1930 propagatedBuildInputs = with self; [];
1917 propagatedBuildInputs = with self; [];
1931 src = fetchurl {
1918 src = fetchurl {
1932 url = "https://pypi.python.org/packages/3b/f0/e763169124e3f5db0926bc3dbfcd580a105f9ca44cf5d8e6c7a803c9f6b5/urllib3-1.16.tar.gz";
1919 url = "https://pypi.python.org/packages/3b/f0/e763169124e3f5db0926bc3dbfcd580a105f9ca44cf5d8e6c7a803c9f6b5/urllib3-1.16.tar.gz";
1933 md5 = "fcaab1c5385c57deeb7053d3d7d81d59";
1920 md5 = "fcaab1c5385c57deeb7053d3d7d81d59";
1934 };
1921 };
1935 meta = {
1922 meta = {
1936 license = [ pkgs.lib.licenses.mit ];
1923 license = [ pkgs.lib.licenses.mit ];
1937 };
1924 };
1938 };
1925 };
1939 venusian = super.buildPythonPackage {
1926 venusian = super.buildPythonPackage {
1940 name = "venusian-1.1.0";
1927 name = "venusian-1.1.0";
1941 buildInputs = with self; [];
1928 buildInputs = with self; [];
1942 doCheck = false;
1929 doCheck = false;
1943 propagatedBuildInputs = with self; [];
1930 propagatedBuildInputs = with self; [];
1944 src = fetchurl {
1931 src = fetchurl {
1945 url = "https://pypi.python.org/packages/38/24/b4b470ab9e0a2e2e9b9030c7735828c8934b4c6b45befd1bb713ec2aeb2d/venusian-1.1.0.tar.gz";
1932 url = "https://pypi.python.org/packages/38/24/b4b470ab9e0a2e2e9b9030c7735828c8934b4c6b45befd1bb713ec2aeb2d/venusian-1.1.0.tar.gz";
1946 md5 = "56bc5e6756e4bda37bcdb94f74a72b8f";
1933 md5 = "56bc5e6756e4bda37bcdb94f74a72b8f";
1947 };
1934 };
1948 meta = {
1935 meta = {
1949 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1936 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1950 };
1937 };
1951 };
1938 };
1939 vine = super.buildPythonPackage {
1940 name = "vine-1.1.4";
1941 buildInputs = with self; [];
1942 doCheck = false;
1943 propagatedBuildInputs = with self; [];
1944 src = fetchurl {
1945 url = "https://pypi.python.org/packages/32/23/36284986e011f3c130d802c3c66abd8f1aef371eae110ddf80c5ae22e1ff/vine-1.1.4.tar.gz";
1946 md5 = "9fdb971e7fd15b181b84f3bfcf20d11c";
1947 };
1948 meta = {
1949 license = [ pkgs.lib.licenses.bsdOriginal ];
1950 };
1951 };
1952 waitress = super.buildPythonPackage {
1952 waitress = super.buildPythonPackage {
1953 name = "waitress-1.1.0";
1953 name = "waitress-1.1.0";
1954 buildInputs = with self; [];
1954 buildInputs = with self; [];
1955 doCheck = false;
1955 doCheck = false;
1956 propagatedBuildInputs = with self; [];
1956 propagatedBuildInputs = with self; [];
1957 src = fetchurl {
1957 src = fetchurl {
1958 url = "https://pypi.python.org/packages/3c/68/1c10dd5c556872ceebe88483b0436140048d39de83a84a06a8baa8136f4f/waitress-1.1.0.tar.gz";
1958 url = "https://pypi.python.org/packages/3c/68/1c10dd5c556872ceebe88483b0436140048d39de83a84a06a8baa8136f4f/waitress-1.1.0.tar.gz";
1959 md5 = "0f1eb7fdfdbf2e6d18decbda1733045c";
1959 md5 = "0f1eb7fdfdbf2e6d18decbda1733045c";
1960 };
1960 };
1961 meta = {
1961 meta = {
1962 license = [ pkgs.lib.licenses.zpt21 ];
1962 license = [ pkgs.lib.licenses.zpt21 ];
1963 };
1963 };
1964 };
1964 };
1965 wcwidth = super.buildPythonPackage {
1965 wcwidth = super.buildPythonPackage {
1966 name = "wcwidth-0.1.7";
1966 name = "wcwidth-0.1.7";
1967 buildInputs = with self; [];
1967 buildInputs = with self; [];
1968 doCheck = false;
1968 doCheck = false;
1969 propagatedBuildInputs = with self; [];
1969 propagatedBuildInputs = with self; [];
1970 src = fetchurl {
1970 src = fetchurl {
1971 url = "https://pypi.python.org/packages/55/11/e4a2bb08bb450fdbd42cc709dd40de4ed2c472cf0ccb9e64af22279c5495/wcwidth-0.1.7.tar.gz";
1971 url = "https://pypi.python.org/packages/55/11/e4a2bb08bb450fdbd42cc709dd40de4ed2c472cf0ccb9e64af22279c5495/wcwidth-0.1.7.tar.gz";
1972 md5 = "b3b6a0a08f0c8a34d1de8cf44150a4ad";
1972 md5 = "b3b6a0a08f0c8a34d1de8cf44150a4ad";
1973 };
1973 };
1974 meta = {
1974 meta = {
1975 license = [ pkgs.lib.licenses.mit ];
1975 license = [ pkgs.lib.licenses.mit ];
1976 };
1976 };
1977 };
1977 };
1978 ws4py = super.buildPythonPackage {
1978 ws4py = super.buildPythonPackage {
1979 name = "ws4py-0.4.2";
1979 name = "ws4py-0.4.2";
1980 buildInputs = with self; [];
1980 buildInputs = with self; [];
1981 doCheck = false;
1981 doCheck = false;
1982 propagatedBuildInputs = with self; [];
1982 propagatedBuildInputs = with self; [];
1983 src = fetchurl {
1983 src = fetchurl {
1984 url = "https://pypi.python.org/packages/b8/98/a90f1d96ffcb15dfc220af524ce23e0a5881258dafa197673357ce1683dd/ws4py-0.4.2.tar.gz";
1984 url = "https://pypi.python.org/packages/b8/98/a90f1d96ffcb15dfc220af524ce23e0a5881258dafa197673357ce1683dd/ws4py-0.4.2.tar.gz";
1985 md5 = "f0603ae376707a58d205bd87a67758a2";
1985 md5 = "f0603ae376707a58d205bd87a67758a2";
1986 };
1986 };
1987 meta = {
1987 meta = {
1988 license = [ pkgs.lib.licenses.bsdOriginal ];
1988 license = [ pkgs.lib.licenses.bsdOriginal ];
1989 };
1989 };
1990 };
1990 };
1991 wsgiref = super.buildPythonPackage {
1991 wsgiref = super.buildPythonPackage {
1992 name = "wsgiref-0.1.2";
1992 name = "wsgiref-0.1.2";
1993 buildInputs = with self; [];
1993 buildInputs = with self; [];
1994 doCheck = false;
1994 doCheck = false;
1995 propagatedBuildInputs = with self; [];
1995 propagatedBuildInputs = with self; [];
1996 src = fetchurl {
1996 src = fetchurl {
1997 url = "https://pypi.python.org/packages/41/9e/309259ce8dff8c596e8c26df86dbc4e848b9249fd36797fd60be456f03fc/wsgiref-0.1.2.zip";
1997 url = "https://pypi.python.org/packages/41/9e/309259ce8dff8c596e8c26df86dbc4e848b9249fd36797fd60be456f03fc/wsgiref-0.1.2.zip";
1998 md5 = "29b146e6ebd0f9fb119fe321f7bcf6cb";
1998 md5 = "29b146e6ebd0f9fb119fe321f7bcf6cb";
1999 };
1999 };
2000 meta = {
2000 meta = {
2001 license = [ { fullName = "PSF or ZPL"; } ];
2001 license = [ { fullName = "PSF or ZPL"; } ];
2002 };
2002 };
2003 };
2003 };
2004 zope.cachedescriptors = super.buildPythonPackage {
2004 zope.cachedescriptors = super.buildPythonPackage {
2005 name = "zope.cachedescriptors-4.0.0";
2005 name = "zope.cachedescriptors-4.0.0";
2006 buildInputs = with self; [];
2006 buildInputs = with self; [];
2007 doCheck = false;
2007 doCheck = false;
2008 propagatedBuildInputs = with self; [setuptools];
2008 propagatedBuildInputs = with self; [setuptools];
2009 src = fetchurl {
2009 src = fetchurl {
2010 url = "https://pypi.python.org/packages/40/33/694b6644c37f28553f4b9f20b3c3a20fb709a22574dff20b5bdffb09ecd5/zope.cachedescriptors-4.0.0.tar.gz";
2010 url = "https://pypi.python.org/packages/40/33/694b6644c37f28553f4b9f20b3c3a20fb709a22574dff20b5bdffb09ecd5/zope.cachedescriptors-4.0.0.tar.gz";
2011 md5 = "8d308de8c936792c8e758058fcb7d0f0";
2011 md5 = "8d308de8c936792c8e758058fcb7d0f0";
2012 };
2012 };
2013 meta = {
2013 meta = {
2014 license = [ pkgs.lib.licenses.zpt21 ];
2014 license = [ pkgs.lib.licenses.zpt21 ];
2015 };
2015 };
2016 };
2016 };
2017 zope.deprecation = super.buildPythonPackage {
2017 zope.deprecation = super.buildPythonPackage {
2018 name = "zope.deprecation-4.1.2";
2018 name = "zope.deprecation-4.1.2";
2019 buildInputs = with self; [];
2019 buildInputs = with self; [];
2020 doCheck = false;
2020 doCheck = false;
2021 propagatedBuildInputs = with self; [setuptools];
2021 propagatedBuildInputs = with self; [setuptools];
2022 src = fetchurl {
2022 src = fetchurl {
2023 url = "https://pypi.python.org/packages/c1/d3/3919492d5e57d8dd01b36f30b34fc8404a30577392b1eb817c303499ad20/zope.deprecation-4.1.2.tar.gz";
2023 url = "https://pypi.python.org/packages/c1/d3/3919492d5e57d8dd01b36f30b34fc8404a30577392b1eb817c303499ad20/zope.deprecation-4.1.2.tar.gz";
2024 md5 = "e9a663ded58f4f9f7881beb56cae2782";
2024 md5 = "e9a663ded58f4f9f7881beb56cae2782";
2025 };
2025 };
2026 meta = {
2026 meta = {
2027 license = [ pkgs.lib.licenses.zpt21 ];
2027 license = [ pkgs.lib.licenses.zpt21 ];
2028 };
2028 };
2029 };
2029 };
2030 zope.event = super.buildPythonPackage {
2030 zope.event = super.buildPythonPackage {
2031 name = "zope.event-4.0.3";
2031 name = "zope.event-4.0.3";
2032 buildInputs = with self; [];
2032 buildInputs = with self; [];
2033 doCheck = false;
2033 doCheck = false;
2034 propagatedBuildInputs = with self; [setuptools];
2034 propagatedBuildInputs = with self; [setuptools];
2035 src = fetchurl {
2035 src = fetchurl {
2036 url = "https://pypi.python.org/packages/c1/29/91ba884d7d6d96691df592e9e9c2bfa57a47040ec1ff47eff18c85137152/zope.event-4.0.3.tar.gz";
2036 url = "https://pypi.python.org/packages/c1/29/91ba884d7d6d96691df592e9e9c2bfa57a47040ec1ff47eff18c85137152/zope.event-4.0.3.tar.gz";
2037 md5 = "9a3780916332b18b8b85f522bcc3e249";
2037 md5 = "9a3780916332b18b8b85f522bcc3e249";
2038 };
2038 };
2039 meta = {
2039 meta = {
2040 license = [ pkgs.lib.licenses.zpt21 ];
2040 license = [ pkgs.lib.licenses.zpt21 ];
2041 };
2041 };
2042 };
2042 };
2043 zope.interface = super.buildPythonPackage {
2043 zope.interface = super.buildPythonPackage {
2044 name = "zope.interface-4.1.3";
2044 name = "zope.interface-4.1.3";
2045 buildInputs = with self; [];
2045 buildInputs = with self; [];
2046 doCheck = false;
2046 doCheck = false;
2047 propagatedBuildInputs = with self; [setuptools];
2047 propagatedBuildInputs = with self; [setuptools];
2048 src = fetchurl {
2048 src = fetchurl {
2049 url = "https://pypi.python.org/packages/9d/81/2509ca3c6f59080123c1a8a97125eb48414022618cec0e64eb1313727bfe/zope.interface-4.1.3.tar.gz";
2049 url = "https://pypi.python.org/packages/9d/81/2509ca3c6f59080123c1a8a97125eb48414022618cec0e64eb1313727bfe/zope.interface-4.1.3.tar.gz";
2050 md5 = "9ae3d24c0c7415deb249dd1a132f0f79";
2050 md5 = "9ae3d24c0c7415deb249dd1a132f0f79";
2051 };
2051 };
2052 meta = {
2052 meta = {
2053 license = [ pkgs.lib.licenses.zpt21 ];
2053 license = [ pkgs.lib.licenses.zpt21 ];
2054 };
2054 };
2055 };
2055 };
2056
2056
2057 ### Test requirements
2057 ### Test requirements
2058
2058
2059
2059
2060 }
2060 }
@@ -1,134 +1,131 b''
1 ## core
1 ## core
2 setuptools==30.1.0
2 setuptools==30.1.0
3 setuptools-scm==1.15.0
3 setuptools-scm==1.15.6
4
4
5 amqplib==1.0.2
5 amqplib==1.0.2
6 anyjson==0.3.3
6 amqp==2.2.2
7 authomatic==0.1.0.post1
7 authomatic==0.1.0.post1
8 Babel==1.3
8 Babel==1.3
9 Beaker==1.9.0
9 Beaker==1.9.0
10 celery==2.2.10
10 celery==4.1.0
11 Chameleon==2.24
11 Chameleon==2.24
12 channelstream==0.5.2
12 channelstream==0.5.2
13 click==6.6
13 click==6.6
14 colander==1.4.0
14 colander==1.4.0
15 configobj==5.0.6
15 configobj==5.0.6
16 cssselect==1.0.1
16 cssselect==1.0.1
17 decorator==4.1.2
17 decorator==4.1.2
18 deform==2.0.4
18 deform==2.0.4
19 docutils==0.14.0
19 docutils==0.14.0
20 dogpile.cache==0.6.4
20 dogpile.cache==0.6.4
21 dogpile.core==0.4.1
21 dogpile.core==0.4.1
22 ecdsa==0.13
22 ecdsa==0.13
23 FormEncode==1.2.4
23 FormEncode==1.2.4
24 future==0.14.3
24 future==0.14.3
25 futures==3.0.2
25 futures==3.0.2
26 gnureadline==6.3.8
26 gnureadline==6.3.8
27 infrae.cache==1.0.1
27 infrae.cache==1.0.1
28 iso8601==0.1.12
28 iso8601==0.1.12
29 itsdangerous==0.24
29 itsdangerous==0.24
30 Jinja2==2.9.6
30 Jinja2==2.9.6
31 kombu==1.5.1
31 billiard==3.5.0.3
32 kombu==4.1.0
32 lxml==3.7.3
33 lxml==3.7.3
33 Mako==1.0.7
34 Mako==1.0.7
34 Markdown==2.6.9
35 Markdown==2.6.9
35 MarkupSafe==1.0.0
36 MarkupSafe==1.0.0
36 msgpack-python==0.4.8
37 msgpack-python==0.4.8
37 MySQL-python==1.2.5
38 MySQL-python==1.2.5
38 objgraph==3.1.1
39 objgraph==3.1.1
39 packaging==15.2
40 packaging==15.2
40 Paste==2.0.3
41 Paste==2.0.3
41 PasteDeploy==1.5.2
42 PasteDeploy==1.5.2
42 PasteScript==2.0.2
43 PasteScript==2.0.2
43 pathlib2==2.3.0
44 pathlib2==2.3.0
44 peppercorn==0.5
45 peppercorn==0.5
45 psutil==5.4.0
46 psutil==5.4.0
46 psycopg2==2.7.3.2
47 psycopg2==2.7.3.2
47 py-bcrypt==0.4
48 py-bcrypt==0.4
48 pycrypto==2.6.1
49 pycrypto==2.6.1
49 pycurl==7.19.5
50 pycurl==7.19.5
50 pyflakes==0.8.1
51 pyflakes==0.8.1
51 pygments-markdown-lexer==0.1.0.dev39
52 pygments-markdown-lexer==0.1.0.dev39
52 Pygments==2.2.0
53 Pygments==2.2.0
53 pyparsing==1.5.7
54 pyparsing==1.5.7
54 pyramid-beaker==0.8
55 pyramid-beaker==0.8
55 pyramid-debugtoolbar==4.3.0
56 pyramid-debugtoolbar==4.3.0
56 pyramid-jinja2==2.7
57 pyramid-jinja2==2.7
57 pyramid-mako==1.0.2
58 pyramid-mako==1.0.2
58 pyramid==1.9.1
59 pyramid==1.9.1
59 pysqlite==2.8.3
60 pysqlite==2.8.3
60 python-dateutil==2.1
61 python-dateutil
61 python-ldap==2.4.45
62 python-ldap==2.4.45
62 python-memcached==1.58
63 python-memcached==1.58
63 python-pam==1.8.2
64 python-pam==1.8.2
64 pytz==2015.4
65 pytz==2017.3
65 pyzmq==14.6.0
66 pyzmq==14.6.0
66 py-gfm==0.1.3
67 py-gfm==0.1.3
67 recaptcha-client==1.0.6
68 recaptcha-client==1.0.6
68 redis==2.10.6
69 redis==2.10.6
69 repoze.lru==0.7
70 repoze.lru==0.7
70 requests==2.9.1
71 requests==2.9.1
71 Routes==1.13
72 Routes==2.4.1
72 setproctitle==1.1.10
73 setproctitle==1.1.10
73 simplejson==3.11.1
74 simplejson==3.11.1
74 six==1.11.0
75 six==1.11.0
75 SQLAlchemy==1.1.15
76 SQLAlchemy==1.1.15
76 sshpubkeys==2.2.0
77 sshpubkeys==2.2.0
77 subprocess32==3.2.7
78 subprocess32==3.2.7
78 Tempita==0.5.2
79 Tempita==0.5.2
79 translationstring==1.3
80 translationstring==1.3
80 trollius==1.0.4
81 trollius==1.0.4
81 urllib3==1.16
82 urllib3==1.16
82 URLObject==2.4.0
83 URLObject==2.4.0
83 venusian==1.1.0
84 venusian==1.1.0
84 WebError==0.10.3
85 WebError==0.10.3
85 WebHelpers2==2.0
86 WebHelpers2==2.0
86 WebHelpers==1.3
87 WebHelpers==1.3
87 WebOb==1.7.3
88 WebOb==1.7.3
88 Whoosh==2.7.4
89 Whoosh==2.7.4
89 wsgiref==0.1.2
90 wsgiref==0.1.2
90 zope.cachedescriptors==4.0.0
91 zope.cachedescriptors==4.0.0
91 zope.deprecation==4.1.2
92 zope.deprecation==4.1.2
92 zope.event==4.0.3
93 zope.event==4.0.3
93 zope.interface==4.1.3
94 zope.interface==4.1.3
94
95
95 ## customized/patched libs
96 # our patched version of Pylons==1.0.2
97 https://code.rhodecode.com/upstream/pylons/archive/707354ee4261b9c10450404fc9852ccea4fd667d.tar.gz?md5=f26633726fa2cd3a340316ee6a5d218f#egg=Pylons==1.0.2.rhodecode-patch-1
98
99
96
100 # IPYTHON RENDERING
97 # IPYTHON RENDERING
101 # entrypoints backport, pypi version doesn't support egg installs
98 # entrypoints backport, pypi version doesn't support egg installs
102 https://code.rhodecode.com/upstream/entrypoints/archive/96e6d645684e1af3d7df5b5272f3fe85a546b233.tar.gz?md5=7db37771aea9ac9fefe093e5d6987313#egg=entrypoints==0.2.2.rhodecode-upstream1
99 https://code.rhodecode.com/upstream/entrypoints/archive/96e6d645684e1af3d7df5b5272f3fe85a546b233.tar.gz?md5=7db37771aea9ac9fefe093e5d6987313#egg=entrypoints==0.2.2.rhodecode-upstream1
103 nbconvert==5.1.1
100 nbconvert==5.1.1
104 bleach==1.5.0
101 bleach==1.5.0
105 nbformat==4.3.0
102 nbformat==4.3.0
106 jupyter_client==5.0.0
103 jupyter_client==5.0.0
107
104
108 ## cli tools
105 ## cli tools
109 alembic==0.9.6
106 alembic==0.9.6
110 invoke==0.13.0
107 invoke==0.13.0
111 bumpversion==0.5.3
108 bumpversion==0.5.3
112 transifex-client==0.12.5
109 transifex-client==0.12.5
113
110
114 ## http servers
111 ## http servers
115 gevent==1.2.2
112 gevent==1.2.2
116 greenlet==0.4.12
113 greenlet==0.4.12
117 gunicorn==19.7.1
114 gunicorn==19.7.1
118 waitress==1.1.0
115 waitress==1.1.0
119 uWSGI==2.0.15
116 uWSGI==2.0.15
120
117
121 ## debug
118 ## debug
122 ipdb==0.10.3
119 ipdb==0.10.3
123 ipython==5.1.0
120 ipython==5.1.0
124 CProfileV==1.0.7
121 CProfileV==1.0.7
125 bottle==0.12.13
122 bottle==0.12.13
126
123
127 ## rhodecode-tools, special case
124 ## rhodecode-tools, special case
128 https://code.rhodecode.com/rhodecode-tools-ce/archive/v0.14.0.tar.gz?md5=15de9be3d185d832c4af2156fefc8eeb#egg=rhodecode-tools==0.14.0
125 https://code.rhodecode.com/rhodecode-tools-ce/archive/v0.14.0.tar.gz?md5=15de9be3d185d832c4af2156fefc8eeb#egg=rhodecode-tools==0.14.0
129
126
130 ## appenlight
127 ## appenlight
131 appenlight-client==0.6.22
128 appenlight-client==0.6.22
132
129
133 ## test related requirements
130 ## test related requirements
134 -r requirements_test.txt
131 -r requirements_test.txt
@@ -1,52 +1,52 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.meta import Session
23 from rhodecode.model.meta import Session
24 from rhodecode.model.user import UserModel
24 from rhodecode.model.user import UserModel
25 from rhodecode.model.auth_token import AuthTokenModel
25 from rhodecode.model.auth_token import AuthTokenModel
26 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
26 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
27
27
28
28
29 @pytest.fixture(scope="class")
29 @pytest.fixture(scope="class")
30 def testuser_api(request, pylonsapp):
30 def testuser_api(request, baseapp):
31 cls = request.cls
31 cls = request.cls
32
32
33 # ADMIN USER
33 # ADMIN USER
34 cls.usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
34 cls.usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
35 cls.apikey = cls.usr.api_key
35 cls.apikey = cls.usr.api_key
36
36
37 # REGULAR USER
37 # REGULAR USER
38 cls.test_user = UserModel().create_or_update(
38 cls.test_user = UserModel().create_or_update(
39 username='test-api',
39 username='test-api',
40 password='test',
40 password='test',
41 email='test@api.rhodecode.org',
41 email='test@api.rhodecode.org',
42 firstname='first',
42 firstname='first',
43 lastname='last'
43 lastname='last'
44 )
44 )
45 # create TOKEN for user, if he doesn't have one
45 # create TOKEN for user, if he doesn't have one
46 if not cls.test_user.api_key:
46 if not cls.test_user.api_key:
47 AuthTokenModel().create(
47 AuthTokenModel().create(
48 user=cls.test_user, description=u'TEST_USER_TOKEN')
48 user=cls.test_user, description=u'TEST_USER_TOKEN')
49
49
50 Session().commit()
50 Session().commit()
51 cls.TEST_USER_LOGIN = cls.test_user.username
51 cls.TEST_USER_LOGIN = cls.test_user.username
52 cls.apikey_regular = cls.test_user.api_key
52 cls.apikey_regular = cls.test_user.api_key
@@ -1,582 +1,564 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import time
21 import time
22 import logging
22 import logging
23 import operator
23 import operator
24
24
25 from pyramid.httpexceptions import HTTPFound
25 from pyramid.httpexceptions import HTTPFound
26
26
27 from rhodecode.lib import helpers as h
27 from rhodecode.lib import helpers as h
28 from rhodecode.lib.utils2 import StrictAttributeDict, safe_int, datetime_to_time
28 from rhodecode.lib.utils2 import StrictAttributeDict, safe_int, datetime_to_time
29 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
29 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
30 from rhodecode.model import repo
30 from rhodecode.model import repo
31 from rhodecode.model import repo_group
31 from rhodecode.model import repo_group
32 from rhodecode.model import user_group
32 from rhodecode.model import user_group
33 from rhodecode.model import user
33 from rhodecode.model import user
34 from rhodecode.model.db import User
34 from rhodecode.model.db import User
35 from rhodecode.model.scm import ScmModel
35 from rhodecode.model.scm import ScmModel
36
36
37 log = logging.getLogger(__name__)
37 log = logging.getLogger(__name__)
38
38
39
39
40 ADMIN_PREFIX = '/_admin'
40 ADMIN_PREFIX = '/_admin'
41 STATIC_FILE_PREFIX = '/_static'
41 STATIC_FILE_PREFIX = '/_static'
42
42
43 URL_NAME_REQUIREMENTS = {
43 URL_NAME_REQUIREMENTS = {
44 # group name can have a slash in them, but they must not end with a slash
44 # group name can have a slash in them, but they must not end with a slash
45 'group_name': r'.*?[^/]',
45 'group_name': r'.*?[^/]',
46 'repo_group_name': r'.*?[^/]',
46 'repo_group_name': r'.*?[^/]',
47 # repo names can have a slash in them, but they must not end with a slash
47 # repo names can have a slash in them, but they must not end with a slash
48 'repo_name': r'.*?[^/]',
48 'repo_name': r'.*?[^/]',
49 # file path eats up everything at the end
49 # file path eats up everything at the end
50 'f_path': r'.*',
50 'f_path': r'.*',
51 # reference types
51 # reference types
52 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
52 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
53 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
53 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
54 }
54 }
55
55
56
56
57 def add_route_with_slash(config,name, pattern, **kw):
57 def add_route_with_slash(config,name, pattern, **kw):
58 config.add_route(name, pattern, **kw)
58 config.add_route(name, pattern, **kw)
59 if not pattern.endswith('/'):
59 if not pattern.endswith('/'):
60 config.add_route(name + '_slash', pattern + '/', **kw)
60 config.add_route(name + '_slash', pattern + '/', **kw)
61
61
62
62
63 def add_route_requirements(route_path, requirements=URL_NAME_REQUIREMENTS):
63 def add_route_requirements(route_path, requirements=URL_NAME_REQUIREMENTS):
64 """
64 """
65 Adds regex requirements to pyramid routes using a mapping dict
65 Adds regex requirements to pyramid routes using a mapping dict
66 e.g::
66 e.g::
67 add_route_requirements('{repo_name}/settings')
67 add_route_requirements('{repo_name}/settings')
68 """
68 """
69 for key, regex in requirements.items():
69 for key, regex in requirements.items():
70 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
70 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
71 return route_path
71 return route_path
72
72
73
73
74 def get_format_ref_id(repo):
74 def get_format_ref_id(repo):
75 """Returns a `repo` specific reference formatter function"""
75 """Returns a `repo` specific reference formatter function"""
76 if h.is_svn(repo):
76 if h.is_svn(repo):
77 return _format_ref_id_svn
77 return _format_ref_id_svn
78 else:
78 else:
79 return _format_ref_id
79 return _format_ref_id
80
80
81
81
82 def _format_ref_id(name, raw_id):
82 def _format_ref_id(name, raw_id):
83 """Default formatting of a given reference `name`"""
83 """Default formatting of a given reference `name`"""
84 return name
84 return name
85
85
86
86
87 def _format_ref_id_svn(name, raw_id):
87 def _format_ref_id_svn(name, raw_id):
88 """Special way of formatting a reference for Subversion including path"""
88 """Special way of formatting a reference for Subversion including path"""
89 return '%s@%s' % (name, raw_id)
89 return '%s@%s' % (name, raw_id)
90
90
91
91
92 class TemplateArgs(StrictAttributeDict):
92 class TemplateArgs(StrictAttributeDict):
93 pass
93 pass
94
94
95
95
96 class BaseAppView(object):
96 class BaseAppView(object):
97
97
98 def __init__(self, context, request):
98 def __init__(self, context, request):
99 self.request = request
99 self.request = request
100 self.context = context
100 self.context = context
101 self.session = request.session
101 self.session = request.session
102 self._rhodecode_user = request.user # auth user
102 self._rhodecode_user = request.user # auth user
103 self._rhodecode_db_user = self._rhodecode_user.get_instance()
103 self._rhodecode_db_user = self._rhodecode_user.get_instance()
104 self._maybe_needs_password_change(
104 self._maybe_needs_password_change(
105 request.matched_route.name, self._rhodecode_db_user)
105 request.matched_route.name, self._rhodecode_db_user)
106
106
107 def _maybe_needs_password_change(self, view_name, user_obj):
107 def _maybe_needs_password_change(self, view_name, user_obj):
108 log.debug('Checking if user %s needs password change on view %s',
108 log.debug('Checking if user %s needs password change on view %s',
109 user_obj, view_name)
109 user_obj, view_name)
110 skip_user_views = [
110 skip_user_views = [
111 'logout', 'login',
111 'logout', 'login',
112 'my_account_password', 'my_account_password_update'
112 'my_account_password', 'my_account_password_update'
113 ]
113 ]
114
114
115 if not user_obj:
115 if not user_obj:
116 return
116 return
117
117
118 if user_obj.username == User.DEFAULT_USER:
118 if user_obj.username == User.DEFAULT_USER:
119 return
119 return
120
120
121 now = time.time()
121 now = time.time()
122 should_change = user_obj.user_data.get('force_password_change')
122 should_change = user_obj.user_data.get('force_password_change')
123 change_after = safe_int(should_change) or 0
123 change_after = safe_int(should_change) or 0
124 if should_change and now > change_after:
124 if should_change and now > change_after:
125 log.debug('User %s requires password change', user_obj)
125 log.debug('User %s requires password change', user_obj)
126 h.flash('You are required to change your password', 'warning',
126 h.flash('You are required to change your password', 'warning',
127 ignore_duplicate=True)
127 ignore_duplicate=True)
128
128
129 if view_name not in skip_user_views:
129 if view_name not in skip_user_views:
130 raise HTTPFound(
130 raise HTTPFound(
131 self.request.route_path('my_account_password'))
131 self.request.route_path('my_account_password'))
132
132
133 def _log_creation_exception(self, e, repo_name):
133 def _log_creation_exception(self, e, repo_name):
134 _ = self.request.translate
134 _ = self.request.translate
135 reason = None
135 reason = None
136 if len(e.args) == 2:
136 if len(e.args) == 2:
137 reason = e.args[1]
137 reason = e.args[1]
138
138
139 if reason == 'INVALID_CERTIFICATE':
139 if reason == 'INVALID_CERTIFICATE':
140 log.exception(
140 log.exception(
141 'Exception creating a repository: invalid certificate')
141 'Exception creating a repository: invalid certificate')
142 msg = (_('Error creating repository %s: invalid certificate')
142 msg = (_('Error creating repository %s: invalid certificate')
143 % repo_name)
143 % repo_name)
144 else:
144 else:
145 log.exception("Exception creating a repository")
145 log.exception("Exception creating a repository")
146 msg = (_('Error creating repository %s')
146 msg = (_('Error creating repository %s')
147 % repo_name)
147 % repo_name)
148 return msg
148 return msg
149
149
150 def _get_local_tmpl_context(self, include_app_defaults=True):
150 def _get_local_tmpl_context(self, include_app_defaults=True):
151 c = TemplateArgs()
151 c = TemplateArgs()
152 c.auth_user = self.request.user
152 c.auth_user = self.request.user
153 # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user
153 # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user
154 c.rhodecode_user = self.request.user
154 c.rhodecode_user = self.request.user
155
155
156 if include_app_defaults:
156 if include_app_defaults:
157 from rhodecode.lib.base import attach_context_attributes
157 from rhodecode.lib.base import attach_context_attributes
158 attach_context_attributes(c, self.request, self.request.user.user_id)
158 attach_context_attributes(c, self.request, self.request.user.user_id)
159
159
160 return c
160 return c
161
161
162 def _register_global_c(self, tmpl_args):
162 def _get_template_context(self, tmpl_args, **kwargs):
163 """
164 Registers attributes to pylons global `c`
165 """
166
167 # TODO(marcink): remove once pyramid migration is finished
168 from pylons import tmpl_context as c
169 try:
170 for k, v in tmpl_args.items():
171 setattr(c, k, v)
172 except TypeError:
173 log.exception('Failed to register pylons C')
174 pass
175
176 def _get_template_context(self, tmpl_args):
177 self._register_global_c(tmpl_args)
178
163
179 local_tmpl_args = {
164 local_tmpl_args = {
180 'defaults': {},
165 'defaults': {},
181 'errors': {},
166 'errors': {},
182 # register a fake 'c' to be used in templates instead of global
167 'c': tmpl_args
183 # pylons c, after migration to pyramid we should rename it to 'c'
184 # make sure we replace usage of _c in templates too
185 '_c': tmpl_args
186 }
168 }
187 local_tmpl_args.update(tmpl_args)
169 local_tmpl_args.update(kwargs)
188 return local_tmpl_args
170 return local_tmpl_args
189
171
190 def load_default_context(self):
172 def load_default_context(self):
191 """
173 """
192 example:
174 example:
193
175
194 def load_default_context(self):
176 def load_default_context(self):
195 c = self._get_local_tmpl_context()
177 c = self._get_local_tmpl_context()
196 c.custom_var = 'foobar'
178 c.custom_var = 'foobar'
197 self._register_global_c(c)
179
198 return c
180 return c
199 """
181 """
200 raise NotImplementedError('Needs implementation in view class')
182 raise NotImplementedError('Needs implementation in view class')
201
183
202
184
203 class RepoAppView(BaseAppView):
185 class RepoAppView(BaseAppView):
204
186
205 def __init__(self, context, request):
187 def __init__(self, context, request):
206 super(RepoAppView, self).__init__(context, request)
188 super(RepoAppView, self).__init__(context, request)
207 self.db_repo = request.db_repo
189 self.db_repo = request.db_repo
208 self.db_repo_name = self.db_repo.repo_name
190 self.db_repo_name = self.db_repo.repo_name
209 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
191 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
210
192
211 def _handle_missing_requirements(self, error):
193 def _handle_missing_requirements(self, error):
212 log.error(
194 log.error(
213 'Requirements are missing for repository %s: %s',
195 'Requirements are missing for repository %s: %s',
214 self.db_repo_name, error.message)
196 self.db_repo_name, error.message)
215
197
216 def _get_local_tmpl_context(self, include_app_defaults=False):
198 def _get_local_tmpl_context(self, include_app_defaults=True):
217 _ = self.request.translate
199 _ = self.request.translate
218 c = super(RepoAppView, self)._get_local_tmpl_context(
200 c = super(RepoAppView, self)._get_local_tmpl_context(
219 include_app_defaults=include_app_defaults)
201 include_app_defaults=include_app_defaults)
220
202
221 # register common vars for this type of view
203 # register common vars for this type of view
222 c.rhodecode_db_repo = self.db_repo
204 c.rhodecode_db_repo = self.db_repo
223 c.repo_name = self.db_repo_name
205 c.repo_name = self.db_repo_name
224 c.repository_pull_requests = self.db_repo_pull_requests
206 c.repository_pull_requests = self.db_repo_pull_requests
225
207
226 c.repository_requirements_missing = False
208 c.repository_requirements_missing = False
227 try:
209 try:
228 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
210 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
229 except RepositoryRequirementError as e:
211 except RepositoryRequirementError as e:
230 c.repository_requirements_missing = True
212 c.repository_requirements_missing = True
231 self._handle_missing_requirements(e)
213 self._handle_missing_requirements(e)
232 self.rhodecode_vcs_repo = None
214 self.rhodecode_vcs_repo = None
233
215
234 if (not c.repository_requirements_missing
216 if (not c.repository_requirements_missing
235 and self.rhodecode_vcs_repo is None):
217 and self.rhodecode_vcs_repo is None):
236 # unable to fetch this repo as vcs instance, report back to user
218 # unable to fetch this repo as vcs instance, report back to user
237 h.flash(_(
219 h.flash(_(
238 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
220 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
239 "Please check if it exist, or is not damaged.") %
221 "Please check if it exist, or is not damaged.") %
240 {'repo_name': c.repo_name},
222 {'repo_name': c.repo_name},
241 category='error', ignore_duplicate=True)
223 category='error', ignore_duplicate=True)
242 raise HTTPFound(h.route_path('home'))
224 raise HTTPFound(h.route_path('home'))
243
225
244 return c
226 return c
245
227
246 def _get_f_path(self, matchdict, default=None):
228 def _get_f_path(self, matchdict, default=None):
247 f_path = matchdict.get('f_path')
229 f_path = matchdict.get('f_path')
248 if f_path:
230 if f_path:
249 # fix for multiple initial slashes that causes errors for GIT
231 # fix for multiple initial slashes that causes errors for GIT
250 return f_path.lstrip('/')
232 return f_path.lstrip('/')
251
233
252 return default
234 return default
253
235
254
236
255 class RepoGroupAppView(BaseAppView):
237 class RepoGroupAppView(BaseAppView):
256 def __init__(self, context, request):
238 def __init__(self, context, request):
257 super(RepoGroupAppView, self).__init__(context, request)
239 super(RepoGroupAppView, self).__init__(context, request)
258 self.db_repo_group = request.db_repo_group
240 self.db_repo_group = request.db_repo_group
259 self.db_repo_group_name = self.db_repo_group.group_name
241 self.db_repo_group_name = self.db_repo_group.group_name
260
242
261 def _revoke_perms_on_yourself(self, form_result):
243 def _revoke_perms_on_yourself(self, form_result):
262 _updates = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
244 _updates = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
263 form_result['perm_updates'])
245 form_result['perm_updates'])
264 _additions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
246 _additions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
265 form_result['perm_additions'])
247 form_result['perm_additions'])
266 _deletions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
248 _deletions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
267 form_result['perm_deletions'])
249 form_result['perm_deletions'])
268 admin_perm = 'group.admin'
250 admin_perm = 'group.admin'
269 if _updates and _updates[0][1] != admin_perm or \
251 if _updates and _updates[0][1] != admin_perm or \
270 _additions and _additions[0][1] != admin_perm or \
252 _additions and _additions[0][1] != admin_perm or \
271 _deletions and _deletions[0][1] != admin_perm:
253 _deletions and _deletions[0][1] != admin_perm:
272 return True
254 return True
273 return False
255 return False
274
256
275
257
276 class UserGroupAppView(BaseAppView):
258 class UserGroupAppView(BaseAppView):
277 def __init__(self, context, request):
259 def __init__(self, context, request):
278 super(UserGroupAppView, self).__init__(context, request)
260 super(UserGroupAppView, self).__init__(context, request)
279 self.db_user_group = request.db_user_group
261 self.db_user_group = request.db_user_group
280 self.db_user_group_name = self.db_user_group.users_group_name
262 self.db_user_group_name = self.db_user_group.users_group_name
281
263
282
264
283 class UserAppView(BaseAppView):
265 class UserAppView(BaseAppView):
284 def __init__(self, context, request):
266 def __init__(self, context, request):
285 super(UserAppView, self).__init__(context, request)
267 super(UserAppView, self).__init__(context, request)
286 self.db_user = request.db_user
268 self.db_user = request.db_user
287 self.db_user_id = self.db_user.user_id
269 self.db_user_id = self.db_user.user_id
288
270
289 _ = self.request.translate
271 _ = self.request.translate
290 if not request.db_user_supports_default:
272 if not request.db_user_supports_default:
291 if self.db_user.username == User.DEFAULT_USER:
273 if self.db_user.username == User.DEFAULT_USER:
292 h.flash(_("Editing user `{}` is disabled.".format(
274 h.flash(_("Editing user `{}` is disabled.".format(
293 User.DEFAULT_USER)), category='warning')
275 User.DEFAULT_USER)), category='warning')
294 raise HTTPFound(h.route_path('users'))
276 raise HTTPFound(h.route_path('users'))
295
277
296
278
297 class DataGridAppView(object):
279 class DataGridAppView(object):
298 """
280 """
299 Common class to have re-usable grid rendering components
281 Common class to have re-usable grid rendering components
300 """
282 """
301
283
302 def _extract_ordering(self, request, column_map=None):
284 def _extract_ordering(self, request, column_map=None):
303 column_map = column_map or {}
285 column_map = column_map or {}
304 column_index = safe_int(request.GET.get('order[0][column]'))
286 column_index = safe_int(request.GET.get('order[0][column]'))
305 order_dir = request.GET.get(
287 order_dir = request.GET.get(
306 'order[0][dir]', 'desc')
288 'order[0][dir]', 'desc')
307 order_by = request.GET.get(
289 order_by = request.GET.get(
308 'columns[%s][data][sort]' % column_index, 'name_raw')
290 'columns[%s][data][sort]' % column_index, 'name_raw')
309
291
310 # translate datatable to DB columns
292 # translate datatable to DB columns
311 order_by = column_map.get(order_by) or order_by
293 order_by = column_map.get(order_by) or order_by
312
294
313 search_q = request.GET.get('search[value]')
295 search_q = request.GET.get('search[value]')
314 return search_q, order_by, order_dir
296 return search_q, order_by, order_dir
315
297
316 def _extract_chunk(self, request):
298 def _extract_chunk(self, request):
317 start = safe_int(request.GET.get('start'), 0)
299 start = safe_int(request.GET.get('start'), 0)
318 length = safe_int(request.GET.get('length'), 25)
300 length = safe_int(request.GET.get('length'), 25)
319 draw = safe_int(request.GET.get('draw'))
301 draw = safe_int(request.GET.get('draw'))
320 return draw, start, length
302 return draw, start, length
321
303
322 def _get_order_col(self, order_by, model):
304 def _get_order_col(self, order_by, model):
323 if isinstance(order_by, basestring):
305 if isinstance(order_by, basestring):
324 try:
306 try:
325 return operator.attrgetter(order_by)(model)
307 return operator.attrgetter(order_by)(model)
326 except AttributeError:
308 except AttributeError:
327 return None
309 return None
328 else:
310 else:
329 return order_by
311 return order_by
330
312
331
313
332 class BaseReferencesView(RepoAppView):
314 class BaseReferencesView(RepoAppView):
333 """
315 """
334 Base for reference view for branches, tags and bookmarks.
316 Base for reference view for branches, tags and bookmarks.
335 """
317 """
336 def load_default_context(self):
318 def load_default_context(self):
337 c = self._get_local_tmpl_context()
319 c = self._get_local_tmpl_context()
338
320
339 self._register_global_c(c)
321
340 return c
322 return c
341
323
342 def load_refs_context(self, ref_items, partials_template):
324 def load_refs_context(self, ref_items, partials_template):
343 _render = self.request.get_partial_renderer(partials_template)
325 _render = self.request.get_partial_renderer(partials_template)
344 pre_load = ["author", "date", "message"]
326 pre_load = ["author", "date", "message"]
345
327
346 is_svn = h.is_svn(self.rhodecode_vcs_repo)
328 is_svn = h.is_svn(self.rhodecode_vcs_repo)
347 is_hg = h.is_hg(self.rhodecode_vcs_repo)
329 is_hg = h.is_hg(self.rhodecode_vcs_repo)
348
330
349 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
331 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
350
332
351 closed_refs = {}
333 closed_refs = {}
352 if is_hg:
334 if is_hg:
353 closed_refs = self.rhodecode_vcs_repo.branches_closed
335 closed_refs = self.rhodecode_vcs_repo.branches_closed
354
336
355 data = []
337 data = []
356 for ref_name, commit_id in ref_items:
338 for ref_name, commit_id in ref_items:
357 commit = self.rhodecode_vcs_repo.get_commit(
339 commit = self.rhodecode_vcs_repo.get_commit(
358 commit_id=commit_id, pre_load=pre_load)
340 commit_id=commit_id, pre_load=pre_load)
359 closed = ref_name in closed_refs
341 closed = ref_name in closed_refs
360
342
361 # TODO: johbo: Unify generation of reference links
343 # TODO: johbo: Unify generation of reference links
362 use_commit_id = '/' in ref_name or is_svn
344 use_commit_id = '/' in ref_name or is_svn
363
345
364 if use_commit_id:
346 if use_commit_id:
365 files_url = h.route_path(
347 files_url = h.route_path(
366 'repo_files',
348 'repo_files',
367 repo_name=self.db_repo_name,
349 repo_name=self.db_repo_name,
368 f_path=ref_name if is_svn else '',
350 f_path=ref_name if is_svn else '',
369 commit_id=commit_id)
351 commit_id=commit_id)
370
352
371 else:
353 else:
372 files_url = h.route_path(
354 files_url = h.route_path(
373 'repo_files',
355 'repo_files',
374 repo_name=self.db_repo_name,
356 repo_name=self.db_repo_name,
375 f_path=ref_name if is_svn else '',
357 f_path=ref_name if is_svn else '',
376 commit_id=ref_name,
358 commit_id=ref_name,
377 _query=dict(at=ref_name))
359 _query=dict(at=ref_name))
378
360
379 data.append({
361 data.append({
380 "name": _render('name', ref_name, files_url, closed),
362 "name": _render('name', ref_name, files_url, closed),
381 "name_raw": ref_name,
363 "name_raw": ref_name,
382 "date": _render('date', commit.date),
364 "date": _render('date', commit.date),
383 "date_raw": datetime_to_time(commit.date),
365 "date_raw": datetime_to_time(commit.date),
384 "author": _render('author', commit.author),
366 "author": _render('author', commit.author),
385 "commit": _render(
367 "commit": _render(
386 'commit', commit.message, commit.raw_id, commit.idx),
368 'commit', commit.message, commit.raw_id, commit.idx),
387 "commit_raw": commit.idx,
369 "commit_raw": commit.idx,
388 "compare": _render(
370 "compare": _render(
389 'compare', format_ref_id(ref_name, commit.raw_id)),
371 'compare', format_ref_id(ref_name, commit.raw_id)),
390 })
372 })
391
373
392 return data
374 return data
393
375
394
376
395 class RepoRoutePredicate(object):
377 class RepoRoutePredicate(object):
396 def __init__(self, val, config):
378 def __init__(self, val, config):
397 self.val = val
379 self.val = val
398
380
399 def text(self):
381 def text(self):
400 return 'repo_route = %s' % self.val
382 return 'repo_route = %s' % self.val
401
383
402 phash = text
384 phash = text
403
385
404 def __call__(self, info, request):
386 def __call__(self, info, request):
405
387
406 if hasattr(request, 'vcs_call'):
388 if hasattr(request, 'vcs_call'):
407 # skip vcs calls
389 # skip vcs calls
408 return
390 return
409
391
410 repo_name = info['match']['repo_name']
392 repo_name = info['match']['repo_name']
411 repo_model = repo.RepoModel()
393 repo_model = repo.RepoModel()
412 by_name_match = repo_model.get_by_repo_name(repo_name, cache=True)
394 by_name_match = repo_model.get_by_repo_name(repo_name, cache=True)
413
395
414 def redirect_if_creating(db_repo):
396 def redirect_if_creating(db_repo):
415 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
397 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
416 raise HTTPFound(
398 raise HTTPFound(
417 request.route_path('repo_creating',
399 request.route_path('repo_creating',
418 repo_name=db_repo.repo_name))
400 repo_name=db_repo.repo_name))
419
401
420 if by_name_match:
402 if by_name_match:
421 # register this as request object we can re-use later
403 # register this as request object we can re-use later
422 request.db_repo = by_name_match
404 request.db_repo = by_name_match
423 redirect_if_creating(by_name_match)
405 redirect_if_creating(by_name_match)
424 return True
406 return True
425
407
426 by_id_match = repo_model.get_repo_by_id(repo_name)
408 by_id_match = repo_model.get_repo_by_id(repo_name)
427 if by_id_match:
409 if by_id_match:
428 request.db_repo = by_id_match
410 request.db_repo = by_id_match
429 redirect_if_creating(by_id_match)
411 redirect_if_creating(by_id_match)
430 return True
412 return True
431
413
432 return False
414 return False
433
415
434
416
435 class RepoTypeRoutePredicate(object):
417 class RepoTypeRoutePredicate(object):
436 def __init__(self, val, config):
418 def __init__(self, val, config):
437 self.val = val or ['hg', 'git', 'svn']
419 self.val = val or ['hg', 'git', 'svn']
438
420
439 def text(self):
421 def text(self):
440 return 'repo_accepted_type = %s' % self.val
422 return 'repo_accepted_type = %s' % self.val
441
423
442 phash = text
424 phash = text
443
425
444 def __call__(self, info, request):
426 def __call__(self, info, request):
445 if hasattr(request, 'vcs_call'):
427 if hasattr(request, 'vcs_call'):
446 # skip vcs calls
428 # skip vcs calls
447 return
429 return
448
430
449 rhodecode_db_repo = request.db_repo
431 rhodecode_db_repo = request.db_repo
450
432
451 log.debug(
433 log.debug(
452 '%s checking repo type for %s in %s',
434 '%s checking repo type for %s in %s',
453 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
435 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
454
436
455 if rhodecode_db_repo.repo_type in self.val:
437 if rhodecode_db_repo.repo_type in self.val:
456 return True
438 return True
457 else:
439 else:
458 log.warning('Current view is not supported for repo type:%s',
440 log.warning('Current view is not supported for repo type:%s',
459 rhodecode_db_repo.repo_type)
441 rhodecode_db_repo.repo_type)
460 #
442 #
461 # h.flash(h.literal(
443 # h.flash(h.literal(
462 # _('Action not supported for %s.' % rhodecode_repo.alias)),
444 # _('Action not supported for %s.' % rhodecode_repo.alias)),
463 # category='warning')
445 # category='warning')
464 # return redirect(
446 # return redirect(
465 # route_path('repo_summary', repo_name=cls.rhodecode_db_repo.repo_name))
447 # route_path('repo_summary', repo_name=cls.rhodecode_db_repo.repo_name))
466
448
467 return False
449 return False
468
450
469
451
470 class RepoGroupRoutePredicate(object):
452 class RepoGroupRoutePredicate(object):
471 def __init__(self, val, config):
453 def __init__(self, val, config):
472 self.val = val
454 self.val = val
473
455
474 def text(self):
456 def text(self):
475 return 'repo_group_route = %s' % self.val
457 return 'repo_group_route = %s' % self.val
476
458
477 phash = text
459 phash = text
478
460
479 def __call__(self, info, request):
461 def __call__(self, info, request):
480 if hasattr(request, 'vcs_call'):
462 if hasattr(request, 'vcs_call'):
481 # skip vcs calls
463 # skip vcs calls
482 return
464 return
483
465
484 repo_group_name = info['match']['repo_group_name']
466 repo_group_name = info['match']['repo_group_name']
485 repo_group_model = repo_group.RepoGroupModel()
467 repo_group_model = repo_group.RepoGroupModel()
486 by_name_match = repo_group_model.get_by_group_name(
468 by_name_match = repo_group_model.get_by_group_name(
487 repo_group_name, cache=True)
469 repo_group_name, cache=True)
488
470
489 if by_name_match:
471 if by_name_match:
490 # register this as request object we can re-use later
472 # register this as request object we can re-use later
491 request.db_repo_group = by_name_match
473 request.db_repo_group = by_name_match
492 return True
474 return True
493
475
494 return False
476 return False
495
477
496
478
497 class UserGroupRoutePredicate(object):
479 class UserGroupRoutePredicate(object):
498 def __init__(self, val, config):
480 def __init__(self, val, config):
499 self.val = val
481 self.val = val
500
482
501 def text(self):
483 def text(self):
502 return 'user_group_route = %s' % self.val
484 return 'user_group_route = %s' % self.val
503
485
504 phash = text
486 phash = text
505
487
506 def __call__(self, info, request):
488 def __call__(self, info, request):
507 if hasattr(request, 'vcs_call'):
489 if hasattr(request, 'vcs_call'):
508 # skip vcs calls
490 # skip vcs calls
509 return
491 return
510
492
511 user_group_id = info['match']['user_group_id']
493 user_group_id = info['match']['user_group_id']
512 user_group_model = user_group.UserGroup()
494 user_group_model = user_group.UserGroup()
513 by_id_match = user_group_model.get(
495 by_id_match = user_group_model.get(
514 user_group_id, cache=True)
496 user_group_id, cache=True)
515
497
516 if by_id_match:
498 if by_id_match:
517 # register this as request object we can re-use later
499 # register this as request object we can re-use later
518 request.db_user_group = by_id_match
500 request.db_user_group = by_id_match
519 return True
501 return True
520
502
521 return False
503 return False
522
504
523
505
524 class UserRoutePredicateBase(object):
506 class UserRoutePredicateBase(object):
525 supports_default = None
507 supports_default = None
526
508
527 def __init__(self, val, config):
509 def __init__(self, val, config):
528 self.val = val
510 self.val = val
529
511
530 def text(self):
512 def text(self):
531 raise NotImplementedError()
513 raise NotImplementedError()
532
514
533 def __call__(self, info, request):
515 def __call__(self, info, request):
534 if hasattr(request, 'vcs_call'):
516 if hasattr(request, 'vcs_call'):
535 # skip vcs calls
517 # skip vcs calls
536 return
518 return
537
519
538 user_id = info['match']['user_id']
520 user_id = info['match']['user_id']
539 user_model = user.User()
521 user_model = user.User()
540 by_id_match = user_model.get(
522 by_id_match = user_model.get(
541 user_id, cache=True)
523 user_id, cache=True)
542
524
543 if by_id_match:
525 if by_id_match:
544 # register this as request object we can re-use later
526 # register this as request object we can re-use later
545 request.db_user = by_id_match
527 request.db_user = by_id_match
546 request.db_user_supports_default = self.supports_default
528 request.db_user_supports_default = self.supports_default
547 return True
529 return True
548
530
549 return False
531 return False
550
532
551
533
552 class UserRoutePredicate(UserRoutePredicateBase):
534 class UserRoutePredicate(UserRoutePredicateBase):
553 supports_default = False
535 supports_default = False
554
536
555 def text(self):
537 def text(self):
556 return 'user_route = %s' % self.val
538 return 'user_route = %s' % self.val
557
539
558 phash = text
540 phash = text
559
541
560
542
561 class UserRouteWithDefaultPredicate(UserRoutePredicateBase):
543 class UserRouteWithDefaultPredicate(UserRoutePredicateBase):
562 supports_default = True
544 supports_default = True
563
545
564 def text(self):
546 def text(self):
565 return 'user_with_default_route = %s' % self.val
547 return 'user_with_default_route = %s' % self.val
566
548
567 phash = text
549 phash = text
568
550
569
551
570 def includeme(config):
552 def includeme(config):
571 config.add_route_predicate(
553 config.add_route_predicate(
572 'repo_route', RepoRoutePredicate)
554 'repo_route', RepoRoutePredicate)
573 config.add_route_predicate(
555 config.add_route_predicate(
574 'repo_accepted_types', RepoTypeRoutePredicate)
556 'repo_accepted_types', RepoTypeRoutePredicate)
575 config.add_route_predicate(
557 config.add_route_predicate(
576 'repo_group_route', RepoGroupRoutePredicate)
558 'repo_group_route', RepoGroupRoutePredicate)
577 config.add_route_predicate(
559 config.add_route_predicate(
578 'user_group_route', UserGroupRoutePredicate)
560 'user_group_route', UserGroupRoutePredicate)
579 config.add_route_predicate(
561 config.add_route_predicate(
580 'user_route_with_default', UserRouteWithDefaultPredicate)
562 'user_route_with_default', UserRouteWithDefaultPredicate)
581 config.add_route_predicate(
563 config.add_route_predicate(
582 'user_route', UserRoutePredicate) No newline at end of file
564 'user_route', UserRoutePredicate)
@@ -1,136 +1,135 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import logging
22 import logging
23 import collections
23 import collections
24
24
25 from zope.interface import implementer
25 from zope.interface import implementer
26
26
27 from rhodecode.apps.admin.interfaces import IAdminNavigationRegistry
27 from rhodecode.apps.admin.interfaces import IAdminNavigationRegistry
28 from rhodecode.lib.utils import get_registry
29 from rhodecode.lib.utils2 import str2bool
28 from rhodecode.lib.utils2 import str2bool
30 from rhodecode.translation import _
29 from rhodecode.translation import _
31
30
32
31
33 log = logging.getLogger(__name__)
32 log = logging.getLogger(__name__)
34
33
35 NavListEntry = collections.namedtuple('NavListEntry', ['key', 'name', 'url'])
34 NavListEntry = collections.namedtuple('NavListEntry', ['key', 'name', 'url'])
36
35
37
36
38 class NavEntry(object):
37 class NavEntry(object):
39 """
38 """
40 Represents an entry in the admin navigation.
39 Represents an entry in the admin navigation.
41
40
42 :param key: Unique identifier used to store reference in an OrderedDict.
41 :param key: Unique identifier used to store reference in an OrderedDict.
43 :param name: Display name, usually a translation string.
42 :param name: Display name, usually a translation string.
44 :param view_name: Name of the view, used generate the URL.
43 :param view_name: Name of the view, used generate the URL.
45 :param pyramid: Indicator to use pyramid for URL generation. This should
44 :param pyramid: Indicator to use pyramid for URL generation. This should
46 be removed as soon as we are fully migrated to pyramid.
45 be removed as soon as we are fully migrated to pyramid.
47 """
46 """
48
47
49 def __init__(self, key, name, view_name):
48 def __init__(self, key, name, view_name):
50 self.key = key
49 self.key = key
51 self.name = name
50 self.name = name
52 self.view_name = view_name
51 self.view_name = view_name
53
52
54 def generate_url(self, request):
53 def generate_url(self, request):
55 return request.route_path(self.view_name)
54 return request.route_path(self.view_name)
56
55
57 def get_localized_name(self, request):
56 def get_localized_name(self, request):
58 return request.translate(self.name)
57 return request.translate(self.name)
59
58
60
59
61 @implementer(IAdminNavigationRegistry)
60 @implementer(IAdminNavigationRegistry)
62 class NavigationRegistry(object):
61 class NavigationRegistry(object):
63
62
64 _base_entries = [
63 _base_entries = [
65 NavEntry('global', _('Global'),
64 NavEntry('global', _('Global'),
66 'admin_settings_global'),
65 'admin_settings_global'),
67 NavEntry('vcs', _('VCS'),
66 NavEntry('vcs', _('VCS'),
68 'admin_settings_vcs'),
67 'admin_settings_vcs'),
69 NavEntry('visual', _('Visual'),
68 NavEntry('visual', _('Visual'),
70 'admin_settings_visual'),
69 'admin_settings_visual'),
71 NavEntry('mapping', _('Remap and Rescan'),
70 NavEntry('mapping', _('Remap and Rescan'),
72 'admin_settings_mapping'),
71 'admin_settings_mapping'),
73 NavEntry('issuetracker', _('Issue Tracker'),
72 NavEntry('issuetracker', _('Issue Tracker'),
74 'admin_settings_issuetracker'),
73 'admin_settings_issuetracker'),
75 NavEntry('email', _('Email'),
74 NavEntry('email', _('Email'),
76 'admin_settings_email'),
75 'admin_settings_email'),
77 NavEntry('hooks', _('Hooks'),
76 NavEntry('hooks', _('Hooks'),
78 'admin_settings_hooks'),
77 'admin_settings_hooks'),
79 NavEntry('search', _('Full Text Search'),
78 NavEntry('search', _('Full Text Search'),
80 'admin_settings_search'),
79 'admin_settings_search'),
81 NavEntry('integrations', _('Integrations'),
80 NavEntry('integrations', _('Integrations'),
82 'global_integrations_home'),
81 'global_integrations_home'),
83 NavEntry('system', _('System Info'),
82 NavEntry('system', _('System Info'),
84 'admin_settings_system'),
83 'admin_settings_system'),
85 NavEntry('process_management', _('Processes'),
84 NavEntry('process_management', _('Processes'),
86 'admin_settings_process_management'),
85 'admin_settings_process_management'),
87 NavEntry('sessions', _('User Sessions'),
86 NavEntry('sessions', _('User Sessions'),
88 'admin_settings_sessions'),
87 'admin_settings_sessions'),
89 NavEntry('open_source', _('Open Source Licenses'),
88 NavEntry('open_source', _('Open Source Licenses'),
90 'admin_settings_open_source'),
89 'admin_settings_open_source'),
91
90
92 ]
91 ]
93
92
94 _labs_entry = NavEntry('labs', _('Labs'),
93 _labs_entry = NavEntry('labs', _('Labs'),
95 'admin_settings_labs')
94 'admin_settings_labs')
96
95
97 def __init__(self, labs_active=False):
96 def __init__(self, labs_active=False):
98 self._registered_entries = collections.OrderedDict()
97 self._registered_entries = collections.OrderedDict()
99 for item in self.__class__._base_entries:
98 for item in self.__class__._base_entries:
100 self._registered_entries[item.key] = item
99 self._registered_entries[item.key] = item
101
100
102 if labs_active:
101 if labs_active:
103 self.add_entry(self._labs_entry)
102 self.add_entry(self._labs_entry)
104
103
105 def add_entry(self, entry):
104 def add_entry(self, entry):
106 self._registered_entries[entry.key] = entry
105 self._registered_entries[entry.key] = entry
107
106
108 def get_navlist(self, request):
107 def get_navlist(self, request):
109 navlist = [NavListEntry(i.key, i.get_localized_name(request),
108 navlist = [NavListEntry(i.key, i.get_localized_name(request),
110 i.generate_url(request))
109 i.generate_url(request))
111 for i in self._registered_entries.values()]
110 for i in self._registered_entries.values()]
112 return navlist
111 return navlist
113
112
114
113
115 def navigation_registry(request, registry=None):
114 def navigation_registry(request, registry=None):
116 """
115 """
117 Helper that returns the admin navigation registry.
116 Helper that returns the admin navigation registry.
118 """
117 """
119 pyramid_registry = registry or get_registry(request)
118 pyramid_registry = registry or request.registry
120 nav_registry = pyramid_registry.queryUtility(IAdminNavigationRegistry)
119 nav_registry = pyramid_registry.queryUtility(IAdminNavigationRegistry)
121 return nav_registry
120 return nav_registry
122
121
123
122
124 def navigation_list(request):
123 def navigation_list(request):
125 """
124 """
126 Helper that returns the admin navigation as list of NavListEntry objects.
125 Helper that returns the admin navigation as list of NavListEntry objects.
127 """
126 """
128 return navigation_registry(request).get_navlist(request)
127 return navigation_registry(request).get_navlist(request)
129
128
130
129
131 def includeme(config):
130 def includeme(config):
132 # Create admin navigation registry and add it to the pyramid registry.
131 # Create admin navigation registry and add it to the pyramid registry.
133 settings = config.get_settings()
132 settings = config.get_settings()
134 labs_active = str2bool(settings.get('labs_settings_active', False))
133 labs_active = str2bool(settings.get('labs_settings_active', False))
135 navigation_registry = NavigationRegistry(labs_active=labs_active)
134 navigation_registry = NavigationRegistry(labs_active=labs_active)
136 config.registry.registerUtility(navigation_registry) No newline at end of file
135 config.registry.registerUtility(navigation_registry)
@@ -1,187 +1,171 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import os
21 import os
22 import csv
22 import csv
23 import datetime
23 import datetime
24
24
25 import pytest
25 import pytest
26
26
27 from rhodecode.tests import *
27 from rhodecode.tests import *
28 from rhodecode.tests.fixture import FIXTURES
28 from rhodecode.tests.fixture import FIXTURES
29 from rhodecode.model.db import UserLog
29 from rhodecode.model.db import UserLog
30 from rhodecode.model.meta import Session
30 from rhodecode.model.meta import Session
31 from rhodecode.lib.utils2 import safe_unicode
31 from rhodecode.lib.utils2 import safe_unicode
32
32
33
33
34 def route_path(name, params=None, **kwargs):
34 def route_path(name, params=None, **kwargs):
35 import urllib
35 import urllib
36 from rhodecode.apps._base import ADMIN_PREFIX
36 from rhodecode.apps._base import ADMIN_PREFIX
37
37
38 base_url = {
38 base_url = {
39 'admin_home': ADMIN_PREFIX,
39 'admin_home': ADMIN_PREFIX,
40 'admin_audit_logs': ADMIN_PREFIX + '/audit_logs',
40 'admin_audit_logs': ADMIN_PREFIX + '/audit_logs',
41
41
42 }[name].format(**kwargs)
42 }[name].format(**kwargs)
43
43
44 if params:
44 if params:
45 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
45 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
46 return base_url
46 return base_url
47
47
48
48
49 class TestAdminController(TestController):
49 @pytest.mark.usefixtures('app')
50 class TestAdminController(object):
50
51
51 @pytest.fixture(scope='class', autouse=True)
52 @pytest.fixture(scope='class', autouse=True)
52 def prepare(self, request, pylonsapp):
53 def prepare(self, request, baseapp):
53 UserLog.query().delete()
54 UserLog.query().delete()
54 Session().commit()
55 Session().commit()
55
56
56 def strptime(val):
57 def strptime(val):
57 fmt = '%Y-%m-%d %H:%M:%S'
58 fmt = '%Y-%m-%d %H:%M:%S'
58 if '.' not in val:
59 if '.' not in val:
59 return datetime.datetime.strptime(val, fmt)
60 return datetime.datetime.strptime(val, fmt)
60
61
61 nofrag, frag = val.split(".")
62 nofrag, frag = val.split(".")
62 date = datetime.datetime.strptime(nofrag, fmt)
63 date = datetime.datetime.strptime(nofrag, fmt)
63
64
64 frag = frag[:6] # truncate to microseconds
65 frag = frag[:6] # truncate to microseconds
65 frag += (6 - len(frag)) * '0' # add 0s
66 frag += (6 - len(frag)) * '0' # add 0s
66 return date.replace(microsecond=int(frag))
67 return date.replace(microsecond=int(frag))
67
68
68 with open(os.path.join(FIXTURES, 'journal_dump.csv')) as f:
69 with open(os.path.join(FIXTURES, 'journal_dump.csv')) as f:
69 for row in csv.DictReader(f):
70 for row in csv.DictReader(f):
70 ul = UserLog()
71 ul = UserLog()
71 for k, v in row.iteritems():
72 for k, v in row.iteritems():
72 v = safe_unicode(v)
73 v = safe_unicode(v)
73 if k == 'action_date':
74 if k == 'action_date':
74 v = strptime(v)
75 v = strptime(v)
75 if k in ['user_id', 'repository_id']:
76 if k in ['user_id', 'repository_id']:
76 # nullable due to FK problems
77 # nullable due to FK problems
77 v = None
78 v = None
78 setattr(ul, k, v)
79 setattr(ul, k, v)
79 Session().add(ul)
80 Session().add(ul)
80 Session().commit()
81 Session().commit()
81
82
82 @request.addfinalizer
83 @request.addfinalizer
83 def cleanup():
84 def cleanup():
84 UserLog.query().delete()
85 UserLog.query().delete()
85 Session().commit()
86 Session().commit()
86
87
87 def test_index(self):
88 def test_index(self, autologin_user):
88 self.log_user()
89 response = self.app.get(route_path('admin_audit_logs'))
89 response = self.app.get(route_path('admin_audit_logs'))
90 response.mustcontain('Admin audit logs')
90 response.mustcontain('Admin audit logs')
91
91
92 def test_filter_all_entries(self):
92 def test_filter_all_entries(self, autologin_user):
93 self.log_user()
94 response = self.app.get(route_path('admin_audit_logs'))
93 response = self.app.get(route_path('admin_audit_logs'))
95 all_count = UserLog.query().count()
94 all_count = UserLog.query().count()
96 response.mustcontain('%s entries' % all_count)
95 response.mustcontain('%s entries' % all_count)
97
96
98 def test_filter_journal_filter_exact_match_on_repository(self):
97 def test_filter_journal_filter_exact_match_on_repository(self, autologin_user):
99 self.log_user()
100 response = self.app.get(route_path('admin_audit_logs',
98 response = self.app.get(route_path('admin_audit_logs',
101 params=dict(filter='repository:rhodecode')))
99 params=dict(filter='repository:rhodecode')))
102 response.mustcontain('3 entries')
100 response.mustcontain('3 entries')
103
101
104 def test_filter_journal_filter_exact_match_on_repository_CamelCase(self):
102 def test_filter_journal_filter_exact_match_on_repository_CamelCase(self, autologin_user):
105 self.log_user()
106 response = self.app.get(route_path('admin_audit_logs',
103 response = self.app.get(route_path('admin_audit_logs',
107 params=dict(filter='repository:RhodeCode')))
104 params=dict(filter='repository:RhodeCode')))
108 response.mustcontain('3 entries')
105 response.mustcontain('3 entries')
109
106
110 def test_filter_journal_filter_wildcard_on_repository(self):
107 def test_filter_journal_filter_wildcard_on_repository(self, autologin_user):
111 self.log_user()
112 response = self.app.get(route_path('admin_audit_logs',
108 response = self.app.get(route_path('admin_audit_logs',
113 params=dict(filter='repository:*test*')))
109 params=dict(filter='repository:*test*')))
114 response.mustcontain('862 entries')
110 response.mustcontain('862 entries')
115
111
116 def test_filter_journal_filter_prefix_on_repository(self):
112 def test_filter_journal_filter_prefix_on_repository(self, autologin_user):
117 self.log_user()
118 response = self.app.get(route_path('admin_audit_logs',
113 response = self.app.get(route_path('admin_audit_logs',
119 params=dict(filter='repository:test*')))
114 params=dict(filter='repository:test*')))
120 response.mustcontain('257 entries')
115 response.mustcontain('257 entries')
121
116
122 def test_filter_journal_filter_prefix_on_repository_CamelCase(self):
117 def test_filter_journal_filter_prefix_on_repository_CamelCase(self, autologin_user):
123 self.log_user()
124 response = self.app.get(route_path('admin_audit_logs',
118 response = self.app.get(route_path('admin_audit_logs',
125 params=dict(filter='repository:Test*')))
119 params=dict(filter='repository:Test*')))
126 response.mustcontain('257 entries')
120 response.mustcontain('257 entries')
127
121
128 def test_filter_journal_filter_prefix_on_repository_and_user(self):
122 def test_filter_journal_filter_prefix_on_repository_and_user(self, autologin_user):
129 self.log_user()
130 response = self.app.get(route_path('admin_audit_logs',
123 response = self.app.get(route_path('admin_audit_logs',
131 params=dict(filter='repository:test* AND username:demo')))
124 params=dict(filter='repository:test* AND username:demo')))
132 response.mustcontain('130 entries')
125 response.mustcontain('130 entries')
133
126
134 def test_filter_journal_filter_prefix_on_repository_or_target_repo(self):
127 def test_filter_journal_filter_prefix_on_repository_or_target_repo(self, autologin_user):
135 self.log_user()
136 response = self.app.get(route_path('admin_audit_logs',
128 response = self.app.get(route_path('admin_audit_logs',
137 params=dict(filter='repository:test* OR repository:rhodecode')))
129 params=dict(filter='repository:test* OR repository:rhodecode')))
138 response.mustcontain('260 entries') # 257 + 3
130 response.mustcontain('260 entries') # 257 + 3
139
131
140 def test_filter_journal_filter_exact_match_on_username(self):
132 def test_filter_journal_filter_exact_match_on_username(self, autologin_user):
141 self.log_user()
142 response = self.app.get(route_path('admin_audit_logs',
133 response = self.app.get(route_path('admin_audit_logs',
143 params=dict(filter='username:demo')))
134 params=dict(filter='username:demo')))
144 response.mustcontain('1087 entries')
135 response.mustcontain('1087 entries')
145
136
146 def test_filter_journal_filter_exact_match_on_username_camelCase(self):
137 def test_filter_journal_filter_exact_match_on_username_camelCase(self, autologin_user):
147 self.log_user()
148 response = self.app.get(route_path('admin_audit_logs',
138 response = self.app.get(route_path('admin_audit_logs',
149 params=dict(filter='username:DemO')))
139 params=dict(filter='username:DemO')))
150 response.mustcontain('1087 entries')
140 response.mustcontain('1087 entries')
151
141
152 def test_filter_journal_filter_wildcard_on_username(self):
142 def test_filter_journal_filter_wildcard_on_username(self, autologin_user):
153 self.log_user()
154 response = self.app.get(route_path('admin_audit_logs',
143 response = self.app.get(route_path('admin_audit_logs',
155 params=dict(filter='username:*test*')))
144 params=dict(filter='username:*test*')))
156 entries_count = UserLog.query().filter(UserLog.username.ilike('%test%')).count()
145 entries_count = UserLog.query().filter(UserLog.username.ilike('%test%')).count()
157 response.mustcontain('{} entries'.format(entries_count))
146 response.mustcontain('{} entries'.format(entries_count))
158
147
159 def test_filter_journal_filter_prefix_on_username(self):
148 def test_filter_journal_filter_prefix_on_username(self, autologin_user):
160 self.log_user()
161 response = self.app.get(route_path('admin_audit_logs',
149 response = self.app.get(route_path('admin_audit_logs',
162 params=dict(filter='username:demo*')))
150 params=dict(filter='username:demo*')))
163 response.mustcontain('1101 entries')
151 response.mustcontain('1101 entries')
164
152
165 def test_filter_journal_filter_prefix_on_user_or_other_user(self):
153 def test_filter_journal_filter_prefix_on_user_or_other_user(self, autologin_user):
166 self.log_user()
167 response = self.app.get(route_path('admin_audit_logs',
154 response = self.app.get(route_path('admin_audit_logs',
168 params=dict(filter='username:demo OR username:volcan')))
155 params=dict(filter='username:demo OR username:volcan')))
169 response.mustcontain('1095 entries') # 1087 + 8
156 response.mustcontain('1095 entries') # 1087 + 8
170
157
171 def test_filter_journal_filter_wildcard_on_action(self):
158 def test_filter_journal_filter_wildcard_on_action(self, autologin_user):
172 self.log_user()
173 response = self.app.get(route_path('admin_audit_logs',
159 response = self.app.get(route_path('admin_audit_logs',
174 params=dict(filter='action:*pull_request*')))
160 params=dict(filter='action:*pull_request*')))
175 response.mustcontain('187 entries')
161 response.mustcontain('187 entries')
176
162
177 def test_filter_journal_filter_on_date(self):
163 def test_filter_journal_filter_on_date(self, autologin_user):
178 self.log_user()
179 response = self.app.get(route_path('admin_audit_logs',
164 response = self.app.get(route_path('admin_audit_logs',
180 params=dict(filter='date:20121010')))
165 params=dict(filter='date:20121010')))
181 response.mustcontain('47 entries')
166 response.mustcontain('47 entries')
182
167
183 def test_filter_journal_filter_on_date_2(self):
168 def test_filter_journal_filter_on_date_2(self, autologin_user):
184 self.log_user()
185 response = self.app.get(route_path('admin_audit_logs',
169 response = self.app.get(route_path('admin_audit_logs',
186 params=dict(filter='date:20121020')))
170 params=dict(filter='date:20121020')))
187 response.mustcontain('17 entries')
171 response.mustcontain('17 entries')
@@ -1,730 +1,730 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 import rhodecode
24 import rhodecode
25 from rhodecode.apps._base import ADMIN_PREFIX
25 from rhodecode.apps._base import ADMIN_PREFIX
26 from rhodecode.lib.utils2 import md5
26 from rhodecode.lib.utils2 import md5
27 from rhodecode.model.db import RhodeCodeUi
27 from rhodecode.model.db import RhodeCodeUi
28 from rhodecode.model.meta import Session
28 from rhodecode.model.meta import Session
29 from rhodecode.model.settings import SettingsModel, IssueTrackerSettingsModel
29 from rhodecode.model.settings import SettingsModel, IssueTrackerSettingsModel
30 from rhodecode.tests import assert_session_flash
30 from rhodecode.tests import assert_session_flash
31 from rhodecode.tests.utils import AssertResponse
31 from rhodecode.tests.utils import AssertResponse
32
32
33
33
34 UPDATE_DATA_QUALNAME = (
34 UPDATE_DATA_QUALNAME = (
35 'rhodecode.apps.admin.views.system_info.AdminSystemInfoSettingsView.get_update_data')
35 'rhodecode.apps.admin.views.system_info.AdminSystemInfoSettingsView.get_update_data')
36
36
37
37
38 def route_path(name, params=None, **kwargs):
38 def route_path(name, params=None, **kwargs):
39 import urllib
39 import urllib
40 from rhodecode.apps._base import ADMIN_PREFIX
40 from rhodecode.apps._base import ADMIN_PREFIX
41
41
42 base_url = {
42 base_url = {
43
43
44 'admin_settings':
44 'admin_settings':
45 ADMIN_PREFIX +'/settings',
45 ADMIN_PREFIX +'/settings',
46 'admin_settings_update':
46 'admin_settings_update':
47 ADMIN_PREFIX + '/settings/update',
47 ADMIN_PREFIX + '/settings/update',
48 'admin_settings_global':
48 'admin_settings_global':
49 ADMIN_PREFIX + '/settings/global',
49 ADMIN_PREFIX + '/settings/global',
50 'admin_settings_global_update':
50 'admin_settings_global_update':
51 ADMIN_PREFIX + '/settings/global/update',
51 ADMIN_PREFIX + '/settings/global/update',
52 'admin_settings_vcs':
52 'admin_settings_vcs':
53 ADMIN_PREFIX + '/settings/vcs',
53 ADMIN_PREFIX + '/settings/vcs',
54 'admin_settings_vcs_update':
54 'admin_settings_vcs_update':
55 ADMIN_PREFIX + '/settings/vcs/update',
55 ADMIN_PREFIX + '/settings/vcs/update',
56 'admin_settings_vcs_svn_pattern_delete':
56 'admin_settings_vcs_svn_pattern_delete':
57 ADMIN_PREFIX + '/settings/vcs/svn_pattern_delete',
57 ADMIN_PREFIX + '/settings/vcs/svn_pattern_delete',
58 'admin_settings_mapping':
58 'admin_settings_mapping':
59 ADMIN_PREFIX + '/settings/mapping',
59 ADMIN_PREFIX + '/settings/mapping',
60 'admin_settings_mapping_update':
60 'admin_settings_mapping_update':
61 ADMIN_PREFIX + '/settings/mapping/update',
61 ADMIN_PREFIX + '/settings/mapping/update',
62 'admin_settings_visual':
62 'admin_settings_visual':
63 ADMIN_PREFIX + '/settings/visual',
63 ADMIN_PREFIX + '/settings/visual',
64 'admin_settings_visual_update':
64 'admin_settings_visual_update':
65 ADMIN_PREFIX + '/settings/visual/update',
65 ADMIN_PREFIX + '/settings/visual/update',
66 'admin_settings_issuetracker':
66 'admin_settings_issuetracker':
67 ADMIN_PREFIX + '/settings/issue-tracker',
67 ADMIN_PREFIX + '/settings/issue-tracker',
68 'admin_settings_issuetracker_update':
68 'admin_settings_issuetracker_update':
69 ADMIN_PREFIX + '/settings/issue-tracker/update',
69 ADMIN_PREFIX + '/settings/issue-tracker/update',
70 'admin_settings_issuetracker_test':
70 'admin_settings_issuetracker_test':
71 ADMIN_PREFIX + '/settings/issue-tracker/test',
71 ADMIN_PREFIX + '/settings/issue-tracker/test',
72 'admin_settings_issuetracker_delete':
72 'admin_settings_issuetracker_delete':
73 ADMIN_PREFIX + '/settings/issue-tracker/delete',
73 ADMIN_PREFIX + '/settings/issue-tracker/delete',
74 'admin_settings_email':
74 'admin_settings_email':
75 ADMIN_PREFIX + '/settings/email',
75 ADMIN_PREFIX + '/settings/email',
76 'admin_settings_email_update':
76 'admin_settings_email_update':
77 ADMIN_PREFIX + '/settings/email/update',
77 ADMIN_PREFIX + '/settings/email/update',
78 'admin_settings_hooks':
78 'admin_settings_hooks':
79 ADMIN_PREFIX + '/settings/hooks',
79 ADMIN_PREFIX + '/settings/hooks',
80 'admin_settings_hooks_update':
80 'admin_settings_hooks_update':
81 ADMIN_PREFIX + '/settings/hooks/update',
81 ADMIN_PREFIX + '/settings/hooks/update',
82 'admin_settings_hooks_delete':
82 'admin_settings_hooks_delete':
83 ADMIN_PREFIX + '/settings/hooks/delete',
83 ADMIN_PREFIX + '/settings/hooks/delete',
84 'admin_settings_search':
84 'admin_settings_search':
85 ADMIN_PREFIX + '/settings/search',
85 ADMIN_PREFIX + '/settings/search',
86 'admin_settings_labs':
86 'admin_settings_labs':
87 ADMIN_PREFIX + '/settings/labs',
87 ADMIN_PREFIX + '/settings/labs',
88 'admin_settings_labs_update':
88 'admin_settings_labs_update':
89 ADMIN_PREFIX + '/settings/labs/update',
89 ADMIN_PREFIX + '/settings/labs/update',
90
90
91 'admin_settings_sessions':
91 'admin_settings_sessions':
92 ADMIN_PREFIX + '/settings/sessions',
92 ADMIN_PREFIX + '/settings/sessions',
93 'admin_settings_sessions_cleanup':
93 'admin_settings_sessions_cleanup':
94 ADMIN_PREFIX + '/settings/sessions/cleanup',
94 ADMIN_PREFIX + '/settings/sessions/cleanup',
95 'admin_settings_system':
95 'admin_settings_system':
96 ADMIN_PREFIX + '/settings/system',
96 ADMIN_PREFIX + '/settings/system',
97 'admin_settings_system_update':
97 'admin_settings_system_update':
98 ADMIN_PREFIX + '/settings/system/updates',
98 ADMIN_PREFIX + '/settings/system/updates',
99 'admin_settings_open_source':
99 'admin_settings_open_source':
100 ADMIN_PREFIX + '/settings/open_source',
100 ADMIN_PREFIX + '/settings/open_source',
101
101
102
102
103 }[name].format(**kwargs)
103 }[name].format(**kwargs)
104
104
105 if params:
105 if params:
106 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
106 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
107 return base_url
107 return base_url
108
108
109
109
110 @pytest.mark.usefixtures('autologin_user', 'app')
110 @pytest.mark.usefixtures('autologin_user', 'app')
111 class TestAdminSettingsController(object):
111 class TestAdminSettingsController(object):
112
112
113 @pytest.mark.parametrize('urlname', [
113 @pytest.mark.parametrize('urlname', [
114 'admin_settings_vcs',
114 'admin_settings_vcs',
115 'admin_settings_mapping',
115 'admin_settings_mapping',
116 'admin_settings_global',
116 'admin_settings_global',
117 'admin_settings_visual',
117 'admin_settings_visual',
118 'admin_settings_email',
118 'admin_settings_email',
119 'admin_settings_hooks',
119 'admin_settings_hooks',
120 'admin_settings_search',
120 'admin_settings_search',
121 ])
121 ])
122 def test_simple_get(self, urlname):
122 def test_simple_get(self, urlname):
123 self.app.get(route_path(urlname))
123 self.app.get(route_path(urlname))
124
124
125 def test_create_custom_hook(self, csrf_token):
125 def test_create_custom_hook(self, csrf_token):
126 response = self.app.post(
126 response = self.app.post(
127 route_path('admin_settings_hooks_update'),
127 route_path('admin_settings_hooks_update'),
128 params={
128 params={
129 'new_hook_ui_key': 'test_hooks_1',
129 'new_hook_ui_key': 'test_hooks_1',
130 'new_hook_ui_value': 'cd /tmp',
130 'new_hook_ui_value': 'cd /tmp',
131 'csrf_token': csrf_token})
131 'csrf_token': csrf_token})
132
132
133 response = response.follow()
133 response = response.follow()
134 response.mustcontain('test_hooks_1')
134 response.mustcontain('test_hooks_1')
135 response.mustcontain('cd /tmp')
135 response.mustcontain('cd /tmp')
136
136
137 def test_create_custom_hook_delete(self, csrf_token):
137 def test_create_custom_hook_delete(self, csrf_token):
138 response = self.app.post(
138 response = self.app.post(
139 route_path('admin_settings_hooks_update'),
139 route_path('admin_settings_hooks_update'),
140 params={
140 params={
141 'new_hook_ui_key': 'test_hooks_2',
141 'new_hook_ui_key': 'test_hooks_2',
142 'new_hook_ui_value': 'cd /tmp2',
142 'new_hook_ui_value': 'cd /tmp2',
143 'csrf_token': csrf_token})
143 'csrf_token': csrf_token})
144
144
145 response = response.follow()
145 response = response.follow()
146 response.mustcontain('test_hooks_2')
146 response.mustcontain('test_hooks_2')
147 response.mustcontain('cd /tmp2')
147 response.mustcontain('cd /tmp2')
148
148
149 hook_id = SettingsModel().get_ui_by_key('test_hooks_2').ui_id
149 hook_id = SettingsModel().get_ui_by_key('test_hooks_2').ui_id
150
150
151 # delete
151 # delete
152 self.app.post(
152 self.app.post(
153 route_path('admin_settings_hooks_delete'),
153 route_path('admin_settings_hooks_delete'),
154 params={'hook_id': hook_id, 'csrf_token': csrf_token})
154 params={'hook_id': hook_id, 'csrf_token': csrf_token})
155 response = self.app.get(route_path('admin_settings_hooks'))
155 response = self.app.get(route_path('admin_settings_hooks'))
156 response.mustcontain(no=['test_hooks_2'])
156 response.mustcontain(no=['test_hooks_2'])
157 response.mustcontain(no=['cd /tmp2'])
157 response.mustcontain(no=['cd /tmp2'])
158
158
159
159
160 @pytest.mark.usefixtures('autologin_user', 'app')
160 @pytest.mark.usefixtures('autologin_user', 'app')
161 class TestAdminSettingsGlobal(object):
161 class TestAdminSettingsGlobal(object):
162
162
163 def test_pre_post_code_code_active(self, csrf_token):
163 def test_pre_post_code_code_active(self, csrf_token):
164 pre_code = 'rc-pre-code-187652122'
164 pre_code = 'rc-pre-code-187652122'
165 post_code = 'rc-postcode-98165231'
165 post_code = 'rc-postcode-98165231'
166
166
167 response = self.post_and_verify_settings({
167 response = self.post_and_verify_settings({
168 'rhodecode_pre_code': pre_code,
168 'rhodecode_pre_code': pre_code,
169 'rhodecode_post_code': post_code,
169 'rhodecode_post_code': post_code,
170 'csrf_token': csrf_token,
170 'csrf_token': csrf_token,
171 })
171 })
172
172
173 response = response.follow()
173 response = response.follow()
174 response.mustcontain(pre_code, post_code)
174 response.mustcontain(pre_code, post_code)
175
175
176 def test_pre_post_code_code_inactive(self, csrf_token):
176 def test_pre_post_code_code_inactive(self, csrf_token):
177 pre_code = 'rc-pre-code-187652122'
177 pre_code = 'rc-pre-code-187652122'
178 post_code = 'rc-postcode-98165231'
178 post_code = 'rc-postcode-98165231'
179 response = self.post_and_verify_settings({
179 response = self.post_and_verify_settings({
180 'rhodecode_pre_code': '',
180 'rhodecode_pre_code': '',
181 'rhodecode_post_code': '',
181 'rhodecode_post_code': '',
182 'csrf_token': csrf_token,
182 'csrf_token': csrf_token,
183 })
183 })
184
184
185 response = response.follow()
185 response = response.follow()
186 response.mustcontain(no=[pre_code, post_code])
186 response.mustcontain(no=[pre_code, post_code])
187
187
188 def test_captcha_activate(self, csrf_token):
188 def test_captcha_activate(self, csrf_token):
189 self.post_and_verify_settings({
189 self.post_and_verify_settings({
190 'rhodecode_captcha_private_key': '1234567890',
190 'rhodecode_captcha_private_key': '1234567890',
191 'rhodecode_captcha_public_key': '1234567890',
191 'rhodecode_captcha_public_key': '1234567890',
192 'csrf_token': csrf_token,
192 'csrf_token': csrf_token,
193 })
193 })
194
194
195 response = self.app.get(ADMIN_PREFIX + '/register')
195 response = self.app.get(ADMIN_PREFIX + '/register')
196 response.mustcontain('captcha')
196 response.mustcontain('captcha')
197
197
198 def test_captcha_deactivate(self, csrf_token):
198 def test_captcha_deactivate(self, csrf_token):
199 self.post_and_verify_settings({
199 self.post_and_verify_settings({
200 'rhodecode_captcha_private_key': '',
200 'rhodecode_captcha_private_key': '',
201 'rhodecode_captcha_public_key': '1234567890',
201 'rhodecode_captcha_public_key': '1234567890',
202 'csrf_token': csrf_token,
202 'csrf_token': csrf_token,
203 })
203 })
204
204
205 response = self.app.get(ADMIN_PREFIX + '/register')
205 response = self.app.get(ADMIN_PREFIX + '/register')
206 response.mustcontain(no=['captcha'])
206 response.mustcontain(no=['captcha'])
207
207
208 def test_title_change(self, csrf_token):
208 def test_title_change(self, csrf_token):
209 old_title = 'RhodeCode'
209 old_title = 'RhodeCode'
210
210
211 for new_title in ['Changed', 'Żółwik', old_title]:
211 for new_title in ['Changed', 'Żółwik', old_title]:
212 response = self.post_and_verify_settings({
212 response = self.post_and_verify_settings({
213 'rhodecode_title': new_title,
213 'rhodecode_title': new_title,
214 'csrf_token': csrf_token,
214 'csrf_token': csrf_token,
215 })
215 })
216
216
217 response = response.follow()
217 response = response.follow()
218 response.mustcontain(
218 response.mustcontain(
219 """<div class="branding">- %s</div>""" % new_title)
219 """<div class="branding">- %s</div>""" % new_title)
220
220
221 def post_and_verify_settings(self, settings):
221 def post_and_verify_settings(self, settings):
222 old_title = 'RhodeCode'
222 old_title = 'RhodeCode'
223 old_realm = 'RhodeCode authentication'
223 old_realm = 'RhodeCode authentication'
224 params = {
224 params = {
225 'rhodecode_title': old_title,
225 'rhodecode_title': old_title,
226 'rhodecode_realm': old_realm,
226 'rhodecode_realm': old_realm,
227 'rhodecode_pre_code': '',
227 'rhodecode_pre_code': '',
228 'rhodecode_post_code': '',
228 'rhodecode_post_code': '',
229 'rhodecode_captcha_private_key': '',
229 'rhodecode_captcha_private_key': '',
230 'rhodecode_captcha_public_key': '',
230 'rhodecode_captcha_public_key': '',
231 'rhodecode_create_personal_repo_group': False,
231 'rhodecode_create_personal_repo_group': False,
232 'rhodecode_personal_repo_group_pattern': '${username}',
232 'rhodecode_personal_repo_group_pattern': '${username}',
233 }
233 }
234 params.update(settings)
234 params.update(settings)
235 response = self.app.post(
235 response = self.app.post(
236 route_path('admin_settings_global_update'), params=params)
236 route_path('admin_settings_global_update'), params=params)
237
237
238 assert_session_flash(response, 'Updated application settings')
238 assert_session_flash(response, 'Updated application settings')
239 app_settings = SettingsModel().get_all_settings()
239 app_settings = SettingsModel().get_all_settings()
240 del settings['csrf_token']
240 del settings['csrf_token']
241 for key, value in settings.iteritems():
241 for key, value in settings.iteritems():
242 assert app_settings[key] == value.decode('utf-8')
242 assert app_settings[key] == value.decode('utf-8')
243
243
244 return response
244 return response
245
245
246
246
247 @pytest.mark.usefixtures('autologin_user', 'app')
247 @pytest.mark.usefixtures('autologin_user', 'app')
248 class TestAdminSettingsVcs(object):
248 class TestAdminSettingsVcs(object):
249
249
250 def test_contains_svn_default_patterns(self):
250 def test_contains_svn_default_patterns(self):
251 response = self.app.get(route_path('admin_settings_vcs'))
251 response = self.app.get(route_path('admin_settings_vcs'))
252 expected_patterns = [
252 expected_patterns = [
253 '/trunk',
253 '/trunk',
254 '/branches/*',
254 '/branches/*',
255 '/tags/*',
255 '/tags/*',
256 ]
256 ]
257 for pattern in expected_patterns:
257 for pattern in expected_patterns:
258 response.mustcontain(pattern)
258 response.mustcontain(pattern)
259
259
260 def test_add_new_svn_branch_and_tag_pattern(
260 def test_add_new_svn_branch_and_tag_pattern(
261 self, backend_svn, form_defaults, disable_sql_cache,
261 self, backend_svn, form_defaults, disable_sql_cache,
262 csrf_token):
262 csrf_token):
263 form_defaults.update({
263 form_defaults.update({
264 'new_svn_branch': '/exp/branches/*',
264 'new_svn_branch': '/exp/branches/*',
265 'new_svn_tag': '/important_tags/*',
265 'new_svn_tag': '/important_tags/*',
266 'csrf_token': csrf_token,
266 'csrf_token': csrf_token,
267 })
267 })
268
268
269 response = self.app.post(
269 response = self.app.post(
270 route_path('admin_settings_vcs_update'),
270 route_path('admin_settings_vcs_update'),
271 params=form_defaults, status=302)
271 params=form_defaults, status=302)
272 response = response.follow()
272 response = response.follow()
273
273
274 # Expect to find the new values on the page
274 # Expect to find the new values on the page
275 response.mustcontain('/exp/branches/*')
275 response.mustcontain('/exp/branches/*')
276 response.mustcontain('/important_tags/*')
276 response.mustcontain('/important_tags/*')
277
277
278 # Expect that those patterns are used to match branches and tags now
278 # Expect that those patterns are used to match branches and tags now
279 repo = backend_svn['svn-simple-layout'].scm_instance()
279 repo = backend_svn['svn-simple-layout'].scm_instance()
280 assert 'exp/branches/exp-sphinx-docs' in repo.branches
280 assert 'exp/branches/exp-sphinx-docs' in repo.branches
281 assert 'important_tags/v0.5' in repo.tags
281 assert 'important_tags/v0.5' in repo.tags
282
282
283 def test_add_same_svn_value_twice_shows_an_error_message(
283 def test_add_same_svn_value_twice_shows_an_error_message(
284 self, form_defaults, csrf_token, settings_util):
284 self, form_defaults, csrf_token, settings_util):
285 settings_util.create_rhodecode_ui('vcs_svn_branch', '/test')
285 settings_util.create_rhodecode_ui('vcs_svn_branch', '/test')
286 settings_util.create_rhodecode_ui('vcs_svn_tag', '/test')
286 settings_util.create_rhodecode_ui('vcs_svn_tag', '/test')
287
287
288 response = self.app.post(
288 response = self.app.post(
289 route_path('admin_settings_vcs_update'),
289 route_path('admin_settings_vcs_update'),
290 params={
290 params={
291 'paths_root_path': form_defaults['paths_root_path'],
291 'paths_root_path': form_defaults['paths_root_path'],
292 'new_svn_branch': '/test',
292 'new_svn_branch': '/test',
293 'new_svn_tag': '/test',
293 'new_svn_tag': '/test',
294 'csrf_token': csrf_token,
294 'csrf_token': csrf_token,
295 },
295 },
296 status=200)
296 status=200)
297
297
298 response.mustcontain("Pattern already exists")
298 response.mustcontain("Pattern already exists")
299 response.mustcontain("Some form inputs contain invalid data.")
299 response.mustcontain("Some form inputs contain invalid data.")
300
300
301 @pytest.mark.parametrize('section', [
301 @pytest.mark.parametrize('section', [
302 'vcs_svn_branch',
302 'vcs_svn_branch',
303 'vcs_svn_tag',
303 'vcs_svn_tag',
304 ])
304 ])
305 def test_delete_svn_patterns(
305 def test_delete_svn_patterns(
306 self, section, csrf_token, settings_util):
306 self, section, csrf_token, settings_util):
307 setting = settings_util.create_rhodecode_ui(
307 setting = settings_util.create_rhodecode_ui(
308 section, '/test_delete', cleanup=False)
308 section, '/test_delete', cleanup=False)
309
309
310 self.app.post(
310 self.app.post(
311 route_path('admin_settings_vcs_svn_pattern_delete'),
311 route_path('admin_settings_vcs_svn_pattern_delete'),
312 params={
312 params={
313 'delete_svn_pattern': setting.ui_id,
313 'delete_svn_pattern': setting.ui_id,
314 'csrf_token': csrf_token},
314 'csrf_token': csrf_token},
315 headers={'X-REQUESTED-WITH': 'XMLHttpRequest'})
315 headers={'X-REQUESTED-WITH': 'XMLHttpRequest'})
316
316
317 @pytest.mark.parametrize('section', [
317 @pytest.mark.parametrize('section', [
318 'vcs_svn_branch',
318 'vcs_svn_branch',
319 'vcs_svn_tag',
319 'vcs_svn_tag',
320 ])
320 ])
321 def test_delete_svn_patterns_raises_404_when_no_xhr(
321 def test_delete_svn_patterns_raises_404_when_no_xhr(
322 self, section, csrf_token, settings_util):
322 self, section, csrf_token, settings_util):
323 setting = settings_util.create_rhodecode_ui(section, '/test_delete')
323 setting = settings_util.create_rhodecode_ui(section, '/test_delete')
324
324
325 self.app.post(
325 self.app.post(
326 route_path('admin_settings_vcs_svn_pattern_delete'),
326 route_path('admin_settings_vcs_svn_pattern_delete'),
327 params={
327 params={
328 'delete_svn_pattern': setting.ui_id,
328 'delete_svn_pattern': setting.ui_id,
329 'csrf_token': csrf_token},
329 'csrf_token': csrf_token},
330 status=404)
330 status=404)
331
331
332 def test_extensions_hgsubversion(self, form_defaults, csrf_token):
332 def test_extensions_hgsubversion(self, form_defaults, csrf_token):
333 form_defaults.update({
333 form_defaults.update({
334 'csrf_token': csrf_token,
334 'csrf_token': csrf_token,
335 'extensions_hgsubversion': 'True',
335 'extensions_hgsubversion': 'True',
336 })
336 })
337 response = self.app.post(
337 response = self.app.post(
338 route_path('admin_settings_vcs_update'),
338 route_path('admin_settings_vcs_update'),
339 params=form_defaults,
339 params=form_defaults,
340 status=302)
340 status=302)
341
341
342 response = response.follow()
342 response = response.follow()
343 extensions_input = (
343 extensions_input = (
344 '<input id="extensions_hgsubversion" '
344 '<input id="extensions_hgsubversion" '
345 'name="extensions_hgsubversion" type="checkbox" '
345 'name="extensions_hgsubversion" type="checkbox" '
346 'value="True" checked="checked" />')
346 'value="True" checked="checked" />')
347 response.mustcontain(extensions_input)
347 response.mustcontain(extensions_input)
348
348
349 def test_extensions_hgevolve(self, form_defaults, csrf_token):
349 def test_extensions_hgevolve(self, form_defaults, csrf_token):
350 form_defaults.update({
350 form_defaults.update({
351 'csrf_token': csrf_token,
351 'csrf_token': csrf_token,
352 'extensions_evolve': 'True',
352 'extensions_evolve': 'True',
353 })
353 })
354 response = self.app.post(
354 response = self.app.post(
355 route_path('admin_settings_vcs_update'),
355 route_path('admin_settings_vcs_update'),
356 params=form_defaults,
356 params=form_defaults,
357 status=302)
357 status=302)
358
358
359 response = response.follow()
359 response = response.follow()
360 extensions_input = (
360 extensions_input = (
361 '<input id="extensions_evolve" '
361 '<input id="extensions_evolve" '
362 'name="extensions_evolve" type="checkbox" '
362 'name="extensions_evolve" type="checkbox" '
363 'value="True" checked="checked" />')
363 'value="True" checked="checked" />')
364 response.mustcontain(extensions_input)
364 response.mustcontain(extensions_input)
365
365
366 def test_has_a_section_for_pull_request_settings(self):
366 def test_has_a_section_for_pull_request_settings(self):
367 response = self.app.get(route_path('admin_settings_vcs'))
367 response = self.app.get(route_path('admin_settings_vcs'))
368 response.mustcontain('Pull Request Settings')
368 response.mustcontain('Pull Request Settings')
369
369
370 def test_has_an_input_for_invalidation_of_inline_comments(self):
370 def test_has_an_input_for_invalidation_of_inline_comments(self):
371 response = self.app.get(route_path('admin_settings_vcs'))
371 response = self.app.get(route_path('admin_settings_vcs'))
372 assert_response = AssertResponse(response)
372 assert_response = AssertResponse(response)
373 assert_response.one_element_exists(
373 assert_response.one_element_exists(
374 '[name=rhodecode_use_outdated_comments]')
374 '[name=rhodecode_use_outdated_comments]')
375
375
376 @pytest.mark.parametrize('new_value', [True, False])
376 @pytest.mark.parametrize('new_value', [True, False])
377 def test_allows_to_change_invalidation_of_inline_comments(
377 def test_allows_to_change_invalidation_of_inline_comments(
378 self, form_defaults, csrf_token, new_value):
378 self, form_defaults, csrf_token, new_value):
379 setting_key = 'use_outdated_comments'
379 setting_key = 'use_outdated_comments'
380 setting = SettingsModel().create_or_update_setting(
380 setting = SettingsModel().create_or_update_setting(
381 setting_key, not new_value, 'bool')
381 setting_key, not new_value, 'bool')
382 Session().add(setting)
382 Session().add(setting)
383 Session().commit()
383 Session().commit()
384
384
385 form_defaults.update({
385 form_defaults.update({
386 'csrf_token': csrf_token,
386 'csrf_token': csrf_token,
387 'rhodecode_use_outdated_comments': str(new_value),
387 'rhodecode_use_outdated_comments': str(new_value),
388 })
388 })
389 response = self.app.post(
389 response = self.app.post(
390 route_path('admin_settings_vcs_update'),
390 route_path('admin_settings_vcs_update'),
391 params=form_defaults,
391 params=form_defaults,
392 status=302)
392 status=302)
393 response = response.follow()
393 response = response.follow()
394 setting = SettingsModel().get_setting_by_name(setting_key)
394 setting = SettingsModel().get_setting_by_name(setting_key)
395 assert setting.app_settings_value is new_value
395 assert setting.app_settings_value is new_value
396
396
397 @pytest.mark.parametrize('new_value', [True, False])
397 @pytest.mark.parametrize('new_value', [True, False])
398 def test_allows_to_change_hg_rebase_merge_strategy(
398 def test_allows_to_change_hg_rebase_merge_strategy(
399 self, form_defaults, csrf_token, new_value):
399 self, form_defaults, csrf_token, new_value):
400 setting_key = 'hg_use_rebase_for_merging'
400 setting_key = 'hg_use_rebase_for_merging'
401
401
402 form_defaults.update({
402 form_defaults.update({
403 'csrf_token': csrf_token,
403 'csrf_token': csrf_token,
404 'rhodecode_' + setting_key: str(new_value),
404 'rhodecode_' + setting_key: str(new_value),
405 })
405 })
406
406
407 with mock.patch.dict(
407 with mock.patch.dict(
408 rhodecode.CONFIG, {'labs_settings_active': 'true'}):
408 rhodecode.CONFIG, {'labs_settings_active': 'true'}):
409 self.app.post(
409 self.app.post(
410 route_path('admin_settings_vcs_update'),
410 route_path('admin_settings_vcs_update'),
411 params=form_defaults,
411 params=form_defaults,
412 status=302)
412 status=302)
413
413
414 setting = SettingsModel().get_setting_by_name(setting_key)
414 setting = SettingsModel().get_setting_by_name(setting_key)
415 assert setting.app_settings_value is new_value
415 assert setting.app_settings_value is new_value
416
416
417 @pytest.fixture
417 @pytest.fixture
418 def disable_sql_cache(self, request):
418 def disable_sql_cache(self, request):
419 patcher = mock.patch(
419 patcher = mock.patch(
420 'rhodecode.lib.caching_query.FromCache.process_query')
420 'rhodecode.lib.caching_query.FromCache.process_query')
421 request.addfinalizer(patcher.stop)
421 request.addfinalizer(patcher.stop)
422 patcher.start()
422 patcher.start()
423
423
424 @pytest.fixture
424 @pytest.fixture
425 def form_defaults(self):
425 def form_defaults(self):
426 from rhodecode.apps.admin.views.settings import AdminSettingsView
426 from rhodecode.apps.admin.views.settings import AdminSettingsView
427 return AdminSettingsView._form_defaults()
427 return AdminSettingsView._form_defaults()
428
428
429 # TODO: johbo: What we really want is to checkpoint before a test run and
429 # TODO: johbo: What we really want is to checkpoint before a test run and
430 # reset the session afterwards.
430 # reset the session afterwards.
431 @pytest.fixture(scope='class', autouse=True)
431 @pytest.fixture(scope='class', autouse=True)
432 def cleanup_settings(self, request, pylonsapp):
432 def cleanup_settings(self, request, baseapp):
433 ui_id = RhodeCodeUi.ui_id
433 ui_id = RhodeCodeUi.ui_id
434 original_ids = list(
434 original_ids = list(
435 r.ui_id for r in RhodeCodeUi.query().values(ui_id))
435 r.ui_id for r in RhodeCodeUi.query().values(ui_id))
436
436
437 @request.addfinalizer
437 @request.addfinalizer
438 def cleanup():
438 def cleanup():
439 RhodeCodeUi.query().filter(
439 RhodeCodeUi.query().filter(
440 ui_id.notin_(original_ids)).delete(False)
440 ui_id.notin_(original_ids)).delete(False)
441
441
442
442
443 @pytest.mark.usefixtures('autologin_user', 'app')
443 @pytest.mark.usefixtures('autologin_user', 'app')
444 class TestLabsSettings(object):
444 class TestLabsSettings(object):
445 def test_get_settings_page_disabled(self):
445 def test_get_settings_page_disabled(self):
446 with mock.patch.dict(
446 with mock.patch.dict(
447 rhodecode.CONFIG, {'labs_settings_active': 'false'}):
447 rhodecode.CONFIG, {'labs_settings_active': 'false'}):
448
448
449 response = self.app.get(
449 response = self.app.get(
450 route_path('admin_settings_labs'), status=302)
450 route_path('admin_settings_labs'), status=302)
451
451
452 assert response.location.endswith(route_path('admin_settings'))
452 assert response.location.endswith(route_path('admin_settings'))
453
453
454 def test_get_settings_page_enabled(self):
454 def test_get_settings_page_enabled(self):
455 from rhodecode.apps.admin.views import settings
455 from rhodecode.apps.admin.views import settings
456 lab_settings = [
456 lab_settings = [
457 settings.LabSetting(
457 settings.LabSetting(
458 key='rhodecode_bool',
458 key='rhodecode_bool',
459 type='bool',
459 type='bool',
460 group='bool group',
460 group='bool group',
461 label='bool label',
461 label='bool label',
462 help='bool help'
462 help='bool help'
463 ),
463 ),
464 settings.LabSetting(
464 settings.LabSetting(
465 key='rhodecode_text',
465 key='rhodecode_text',
466 type='unicode',
466 type='unicode',
467 group='text group',
467 group='text group',
468 label='text label',
468 label='text label',
469 help='text help'
469 help='text help'
470 ),
470 ),
471 ]
471 ]
472 with mock.patch.dict(rhodecode.CONFIG,
472 with mock.patch.dict(rhodecode.CONFIG,
473 {'labs_settings_active': 'true'}):
473 {'labs_settings_active': 'true'}):
474 with mock.patch.object(settings, '_LAB_SETTINGS', lab_settings):
474 with mock.patch.object(settings, '_LAB_SETTINGS', lab_settings):
475 response = self.app.get(route_path('admin_settings_labs'))
475 response = self.app.get(route_path('admin_settings_labs'))
476
476
477 assert '<label>bool group:</label>' in response
477 assert '<label>bool group:</label>' in response
478 assert '<label for="rhodecode_bool">bool label</label>' in response
478 assert '<label for="rhodecode_bool">bool label</label>' in response
479 assert '<p class="help-block">bool help</p>' in response
479 assert '<p class="help-block">bool help</p>' in response
480 assert 'name="rhodecode_bool" type="checkbox"' in response
480 assert 'name="rhodecode_bool" type="checkbox"' in response
481
481
482 assert '<label>text group:</label>' in response
482 assert '<label>text group:</label>' in response
483 assert '<label for="rhodecode_text">text label</label>' in response
483 assert '<label for="rhodecode_text">text label</label>' in response
484 assert '<p class="help-block">text help</p>' in response
484 assert '<p class="help-block">text help</p>' in response
485 assert 'name="rhodecode_text" size="60" type="text"' in response
485 assert 'name="rhodecode_text" size="60" type="text"' in response
486
486
487
487
488 @pytest.mark.usefixtures('app')
488 @pytest.mark.usefixtures('app')
489 class TestOpenSourceLicenses(object):
489 class TestOpenSourceLicenses(object):
490
490
491 def test_records_are_displayed(self, autologin_user):
491 def test_records_are_displayed(self, autologin_user):
492 sample_licenses = {
492 sample_licenses = {
493 "python2.7-pytest-2.7.1": {
493 "python2.7-pytest-2.7.1": {
494 "UNKNOWN": None
494 "UNKNOWN": None
495 },
495 },
496 "python2.7-Markdown-2.6.2": {
496 "python2.7-Markdown-2.6.2": {
497 "BSD-3-Clause": "http://spdx.org/licenses/BSD-3-Clause"
497 "BSD-3-Clause": "http://spdx.org/licenses/BSD-3-Clause"
498 }
498 }
499 }
499 }
500 read_licenses_patch = mock.patch(
500 read_licenses_patch = mock.patch(
501 'rhodecode.apps.admin.views.open_source_licenses.read_opensource_licenses',
501 'rhodecode.apps.admin.views.open_source_licenses.read_opensource_licenses',
502 return_value=sample_licenses)
502 return_value=sample_licenses)
503 with read_licenses_patch:
503 with read_licenses_patch:
504 response = self.app.get(
504 response = self.app.get(
505 route_path('admin_settings_open_source'), status=200)
505 route_path('admin_settings_open_source'), status=200)
506
506
507 assert_response = AssertResponse(response)
507 assert_response = AssertResponse(response)
508 assert_response.element_contains(
508 assert_response.element_contains(
509 '.panel-heading', 'Licenses of Third Party Packages')
509 '.panel-heading', 'Licenses of Third Party Packages')
510 for name in sample_licenses:
510 for name in sample_licenses:
511 response.mustcontain(name)
511 response.mustcontain(name)
512 for license in sample_licenses[name]:
512 for license in sample_licenses[name]:
513 assert_response.element_contains('.panel-body', license)
513 assert_response.element_contains('.panel-body', license)
514
514
515 def test_records_can_be_read(self, autologin_user):
515 def test_records_can_be_read(self, autologin_user):
516 response = self.app.get(
516 response = self.app.get(
517 route_path('admin_settings_open_source'), status=200)
517 route_path('admin_settings_open_source'), status=200)
518 assert_response = AssertResponse(response)
518 assert_response = AssertResponse(response)
519 assert_response.element_contains(
519 assert_response.element_contains(
520 '.panel-heading', 'Licenses of Third Party Packages')
520 '.panel-heading', 'Licenses of Third Party Packages')
521
521
522 def test_forbidden_when_normal_user(self, autologin_regular_user):
522 def test_forbidden_when_normal_user(self, autologin_regular_user):
523 self.app.get(
523 self.app.get(
524 route_path('admin_settings_open_source'), status=404)
524 route_path('admin_settings_open_source'), status=404)
525
525
526
526
527 @pytest.mark.usefixtures('app')
527 @pytest.mark.usefixtures('app')
528 class TestUserSessions(object):
528 class TestUserSessions(object):
529
529
530 def test_forbidden_when_normal_user(self, autologin_regular_user):
530 def test_forbidden_when_normal_user(self, autologin_regular_user):
531 self.app.get(route_path('admin_settings_sessions'), status=404)
531 self.app.get(route_path('admin_settings_sessions'), status=404)
532
532
533 def test_show_sessions_page(self, autologin_user):
533 def test_show_sessions_page(self, autologin_user):
534 response = self.app.get(route_path('admin_settings_sessions'), status=200)
534 response = self.app.get(route_path('admin_settings_sessions'), status=200)
535 response.mustcontain('file')
535 response.mustcontain('file')
536
536
537 def test_cleanup_old_sessions(self, autologin_user, csrf_token):
537 def test_cleanup_old_sessions(self, autologin_user, csrf_token):
538
538
539 post_data = {
539 post_data = {
540 'csrf_token': csrf_token,
540 'csrf_token': csrf_token,
541 'expire_days': '60'
541 'expire_days': '60'
542 }
542 }
543 response = self.app.post(
543 response = self.app.post(
544 route_path('admin_settings_sessions_cleanup'), params=post_data,
544 route_path('admin_settings_sessions_cleanup'), params=post_data,
545 status=302)
545 status=302)
546 assert_session_flash(response, 'Cleaned up old sessions')
546 assert_session_flash(response, 'Cleaned up old sessions')
547
547
548
548
549 @pytest.mark.usefixtures('app')
549 @pytest.mark.usefixtures('app')
550 class TestAdminSystemInfo(object):
550 class TestAdminSystemInfo(object):
551
551
552 def test_forbidden_when_normal_user(self, autologin_regular_user):
552 def test_forbidden_when_normal_user(self, autologin_regular_user):
553 self.app.get(route_path('admin_settings_system'), status=404)
553 self.app.get(route_path('admin_settings_system'), status=404)
554
554
555 def test_system_info_page(self, autologin_user):
555 def test_system_info_page(self, autologin_user):
556 response = self.app.get(route_path('admin_settings_system'))
556 response = self.app.get(route_path('admin_settings_system'))
557 response.mustcontain('RhodeCode Community Edition, version {}'.format(
557 response.mustcontain('RhodeCode Community Edition, version {}'.format(
558 rhodecode.__version__))
558 rhodecode.__version__))
559
559
560 def test_system_update_new_version(self, autologin_user):
560 def test_system_update_new_version(self, autologin_user):
561 update_data = {
561 update_data = {
562 'versions': [
562 'versions': [
563 {
563 {
564 'version': '100.3.1415926535',
564 'version': '100.3.1415926535',
565 'general': 'The latest version we are ever going to ship'
565 'general': 'The latest version we are ever going to ship'
566 },
566 },
567 {
567 {
568 'version': '0.0.0',
568 'version': '0.0.0',
569 'general': 'The first version we ever shipped'
569 'general': 'The first version we ever shipped'
570 }
570 }
571 ]
571 ]
572 }
572 }
573 with mock.patch(UPDATE_DATA_QUALNAME, return_value=update_data):
573 with mock.patch(UPDATE_DATA_QUALNAME, return_value=update_data):
574 response = self.app.get(route_path('admin_settings_system_update'))
574 response = self.app.get(route_path('admin_settings_system_update'))
575 response.mustcontain('A <b>new version</b> is available')
575 response.mustcontain('A <b>new version</b> is available')
576
576
577 def test_system_update_nothing_new(self, autologin_user):
577 def test_system_update_nothing_new(self, autologin_user):
578 update_data = {
578 update_data = {
579 'versions': [
579 'versions': [
580 {
580 {
581 'version': '0.0.0',
581 'version': '0.0.0',
582 'general': 'The first version we ever shipped'
582 'general': 'The first version we ever shipped'
583 }
583 }
584 ]
584 ]
585 }
585 }
586 with mock.patch(UPDATE_DATA_QUALNAME, return_value=update_data):
586 with mock.patch(UPDATE_DATA_QUALNAME, return_value=update_data):
587 response = self.app.get(route_path('admin_settings_system_update'))
587 response = self.app.get(route_path('admin_settings_system_update'))
588 response.mustcontain(
588 response.mustcontain(
589 'You already have the <b>latest</b> stable version.')
589 'You already have the <b>latest</b> stable version.')
590
590
591 def test_system_update_bad_response(self, autologin_user):
591 def test_system_update_bad_response(self, autologin_user):
592 with mock.patch(UPDATE_DATA_QUALNAME, side_effect=ValueError('foo')):
592 with mock.patch(UPDATE_DATA_QUALNAME, side_effect=ValueError('foo')):
593 response = self.app.get(route_path('admin_settings_system_update'))
593 response = self.app.get(route_path('admin_settings_system_update'))
594 response.mustcontain(
594 response.mustcontain(
595 'Bad data sent from update server')
595 'Bad data sent from update server')
596
596
597
597
598 @pytest.mark.usefixtures("app")
598 @pytest.mark.usefixtures("app")
599 class TestAdminSettingsIssueTracker(object):
599 class TestAdminSettingsIssueTracker(object):
600 RC_PREFIX = 'rhodecode_'
600 RC_PREFIX = 'rhodecode_'
601 SHORT_PATTERN_KEY = 'issuetracker_pat_'
601 SHORT_PATTERN_KEY = 'issuetracker_pat_'
602 PATTERN_KEY = RC_PREFIX + SHORT_PATTERN_KEY
602 PATTERN_KEY = RC_PREFIX + SHORT_PATTERN_KEY
603
603
604 def test_issuetracker_index(self, autologin_user):
604 def test_issuetracker_index(self, autologin_user):
605 response = self.app.get(route_path('admin_settings_issuetracker'))
605 response = self.app.get(route_path('admin_settings_issuetracker'))
606 assert response.status_code == 200
606 assert response.status_code == 200
607
607
608 def test_add_empty_issuetracker_pattern(
608 def test_add_empty_issuetracker_pattern(
609 self, request, autologin_user, csrf_token):
609 self, request, autologin_user, csrf_token):
610 post_url = route_path('admin_settings_issuetracker_update')
610 post_url = route_path('admin_settings_issuetracker_update')
611 post_data = {
611 post_data = {
612 'csrf_token': csrf_token
612 'csrf_token': csrf_token
613 }
613 }
614 self.app.post(post_url, post_data, status=302)
614 self.app.post(post_url, post_data, status=302)
615
615
616 def test_add_issuetracker_pattern(
616 def test_add_issuetracker_pattern(
617 self, request, autologin_user, csrf_token):
617 self, request, autologin_user, csrf_token):
618 pattern = 'issuetracker_pat'
618 pattern = 'issuetracker_pat'
619 another_pattern = pattern+'1'
619 another_pattern = pattern+'1'
620 post_url = route_path('admin_settings_issuetracker_update')
620 post_url = route_path('admin_settings_issuetracker_update')
621 post_data = {
621 post_data = {
622 'new_pattern_pattern_0': pattern,
622 'new_pattern_pattern_0': pattern,
623 'new_pattern_url_0': 'http://url',
623 'new_pattern_url_0': 'http://url',
624 'new_pattern_prefix_0': 'prefix',
624 'new_pattern_prefix_0': 'prefix',
625 'new_pattern_description_0': 'description',
625 'new_pattern_description_0': 'description',
626 'new_pattern_pattern_1': another_pattern,
626 'new_pattern_pattern_1': another_pattern,
627 'new_pattern_url_1': 'https://url1',
627 'new_pattern_url_1': 'https://url1',
628 'new_pattern_prefix_1': 'prefix1',
628 'new_pattern_prefix_1': 'prefix1',
629 'new_pattern_description_1': 'description1',
629 'new_pattern_description_1': 'description1',
630 'csrf_token': csrf_token
630 'csrf_token': csrf_token
631 }
631 }
632 self.app.post(post_url, post_data, status=302)
632 self.app.post(post_url, post_data, status=302)
633 settings = SettingsModel().get_all_settings()
633 settings = SettingsModel().get_all_settings()
634 self.uid = md5(pattern)
634 self.uid = md5(pattern)
635 assert settings[self.PATTERN_KEY+self.uid] == pattern
635 assert settings[self.PATTERN_KEY+self.uid] == pattern
636 self.another_uid = md5(another_pattern)
636 self.another_uid = md5(another_pattern)
637 assert settings[self.PATTERN_KEY+self.another_uid] == another_pattern
637 assert settings[self.PATTERN_KEY+self.another_uid] == another_pattern
638
638
639 @request.addfinalizer
639 @request.addfinalizer
640 def cleanup():
640 def cleanup():
641 defaults = SettingsModel().get_all_settings()
641 defaults = SettingsModel().get_all_settings()
642
642
643 entries = [name for name in defaults if (
643 entries = [name for name in defaults if (
644 (self.uid in name) or (self.another_uid) in name)]
644 (self.uid in name) or (self.another_uid) in name)]
645 start = len(self.RC_PREFIX)
645 start = len(self.RC_PREFIX)
646 for del_key in entries:
646 for del_key in entries:
647 # TODO: anderson: get_by_name needs name without prefix
647 # TODO: anderson: get_by_name needs name without prefix
648 entry = SettingsModel().get_setting_by_name(del_key[start:])
648 entry = SettingsModel().get_setting_by_name(del_key[start:])
649 Session().delete(entry)
649 Session().delete(entry)
650
650
651 Session().commit()
651 Session().commit()
652
652
653 def test_edit_issuetracker_pattern(
653 def test_edit_issuetracker_pattern(
654 self, autologin_user, backend, csrf_token, request):
654 self, autologin_user, backend, csrf_token, request):
655 old_pattern = 'issuetracker_pat'
655 old_pattern = 'issuetracker_pat'
656 old_uid = md5(old_pattern)
656 old_uid = md5(old_pattern)
657 pattern = 'issuetracker_pat_new'
657 pattern = 'issuetracker_pat_new'
658 self.new_uid = md5(pattern)
658 self.new_uid = md5(pattern)
659
659
660 SettingsModel().create_or_update_setting(
660 SettingsModel().create_or_update_setting(
661 self.SHORT_PATTERN_KEY+old_uid, old_pattern, 'unicode')
661 self.SHORT_PATTERN_KEY+old_uid, old_pattern, 'unicode')
662
662
663 post_url = route_path('admin_settings_issuetracker_update')
663 post_url = route_path('admin_settings_issuetracker_update')
664 post_data = {
664 post_data = {
665 'new_pattern_pattern_0': pattern,
665 'new_pattern_pattern_0': pattern,
666 'new_pattern_url_0': 'https://url',
666 'new_pattern_url_0': 'https://url',
667 'new_pattern_prefix_0': 'prefix',
667 'new_pattern_prefix_0': 'prefix',
668 'new_pattern_description_0': 'description',
668 'new_pattern_description_0': 'description',
669 'uid': old_uid,
669 'uid': old_uid,
670 'csrf_token': csrf_token
670 'csrf_token': csrf_token
671 }
671 }
672 self.app.post(post_url, post_data, status=302)
672 self.app.post(post_url, post_data, status=302)
673 settings = SettingsModel().get_all_settings()
673 settings = SettingsModel().get_all_settings()
674 assert settings[self.PATTERN_KEY+self.new_uid] == pattern
674 assert settings[self.PATTERN_KEY+self.new_uid] == pattern
675 assert self.PATTERN_KEY+old_uid not in settings
675 assert self.PATTERN_KEY+old_uid not in settings
676
676
677 @request.addfinalizer
677 @request.addfinalizer
678 def cleanup():
678 def cleanup():
679 IssueTrackerSettingsModel().delete_entries(self.new_uid)
679 IssueTrackerSettingsModel().delete_entries(self.new_uid)
680
680
681 def test_replace_issuetracker_pattern_description(
681 def test_replace_issuetracker_pattern_description(
682 self, autologin_user, csrf_token, request, settings_util):
682 self, autologin_user, csrf_token, request, settings_util):
683 prefix = 'issuetracker'
683 prefix = 'issuetracker'
684 pattern = 'issuetracker_pat'
684 pattern = 'issuetracker_pat'
685 self.uid = md5(pattern)
685 self.uid = md5(pattern)
686 pattern_key = '_'.join([prefix, 'pat', self.uid])
686 pattern_key = '_'.join([prefix, 'pat', self.uid])
687 rc_pattern_key = '_'.join(['rhodecode', pattern_key])
687 rc_pattern_key = '_'.join(['rhodecode', pattern_key])
688 desc_key = '_'.join([prefix, 'desc', self.uid])
688 desc_key = '_'.join([prefix, 'desc', self.uid])
689 rc_desc_key = '_'.join(['rhodecode', desc_key])
689 rc_desc_key = '_'.join(['rhodecode', desc_key])
690 new_description = 'new_description'
690 new_description = 'new_description'
691
691
692 settings_util.create_rhodecode_setting(
692 settings_util.create_rhodecode_setting(
693 pattern_key, pattern, 'unicode', cleanup=False)
693 pattern_key, pattern, 'unicode', cleanup=False)
694 settings_util.create_rhodecode_setting(
694 settings_util.create_rhodecode_setting(
695 desc_key, 'old description', 'unicode', cleanup=False)
695 desc_key, 'old description', 'unicode', cleanup=False)
696
696
697 post_url = route_path('admin_settings_issuetracker_update')
697 post_url = route_path('admin_settings_issuetracker_update')
698 post_data = {
698 post_data = {
699 'new_pattern_pattern_0': pattern,
699 'new_pattern_pattern_0': pattern,
700 'new_pattern_url_0': 'https://url',
700 'new_pattern_url_0': 'https://url',
701 'new_pattern_prefix_0': 'prefix',
701 'new_pattern_prefix_0': 'prefix',
702 'new_pattern_description_0': new_description,
702 'new_pattern_description_0': new_description,
703 'uid': self.uid,
703 'uid': self.uid,
704 'csrf_token': csrf_token
704 'csrf_token': csrf_token
705 }
705 }
706 self.app.post(post_url, post_data, status=302)
706 self.app.post(post_url, post_data, status=302)
707 settings = SettingsModel().get_all_settings()
707 settings = SettingsModel().get_all_settings()
708 assert settings[rc_pattern_key] == pattern
708 assert settings[rc_pattern_key] == pattern
709 assert settings[rc_desc_key] == new_description
709 assert settings[rc_desc_key] == new_description
710
710
711 @request.addfinalizer
711 @request.addfinalizer
712 def cleanup():
712 def cleanup():
713 IssueTrackerSettingsModel().delete_entries(self.uid)
713 IssueTrackerSettingsModel().delete_entries(self.uid)
714
714
715 def test_delete_issuetracker_pattern(
715 def test_delete_issuetracker_pattern(
716 self, autologin_user, backend, csrf_token, settings_util):
716 self, autologin_user, backend, csrf_token, settings_util):
717 pattern = 'issuetracker_pat'
717 pattern = 'issuetracker_pat'
718 uid = md5(pattern)
718 uid = md5(pattern)
719 settings_util.create_rhodecode_setting(
719 settings_util.create_rhodecode_setting(
720 self.SHORT_PATTERN_KEY+uid, pattern, 'unicode', cleanup=False)
720 self.SHORT_PATTERN_KEY+uid, pattern, 'unicode', cleanup=False)
721
721
722 post_url = route_path('admin_settings_issuetracker_delete')
722 post_url = route_path('admin_settings_issuetracker_delete')
723 post_data = {
723 post_data = {
724 '_method': 'delete',
724 '_method': 'delete',
725 'uid': uid,
725 'uid': uid,
726 'csrf_token': csrf_token
726 'csrf_token': csrf_token
727 }
727 }
728 self.app.post(post_url, post_data, status=302)
728 self.app.post(post_url, post_data, status=302)
729 settings = SettingsModel().get_all_settings()
729 settings = SettingsModel().get_all_settings()
730 assert 'rhodecode_%s%s' % (self.SHORT_PATTERN_KEY, uid) not in settings
730 assert 'rhodecode_%s%s' % (self.SHORT_PATTERN_KEY, uid) not in settings
@@ -1,783 +1,781 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22 from sqlalchemy.orm.exc import NoResultFound
22 from sqlalchemy.orm.exc import NoResultFound
23
23
24 from rhodecode.lib import auth
24 from rhodecode.lib import auth
25 from rhodecode.lib import helpers as h
25 from rhodecode.lib import helpers as h
26 from rhodecode.model import validators
27 from rhodecode.model.db import User, UserApiKeys, UserEmailMap, Repository
26 from rhodecode.model.db import User, UserApiKeys, UserEmailMap, Repository
28 from rhodecode.model.meta import Session
27 from rhodecode.model.meta import Session
29 from rhodecode.model.user import UserModel
28 from rhodecode.model.user import UserModel
30
29
31 from rhodecode.tests import (
30 from rhodecode.tests import (
32 TestController, TEST_USER_REGULAR_LOGIN, assert_session_flash)
31 TestController, TEST_USER_REGULAR_LOGIN, assert_session_flash)
33 from rhodecode.tests.fixture import Fixture
32 from rhodecode.tests.fixture import Fixture
34
33
35 fixture = Fixture()
34 fixture = Fixture()
36
35
37
36
38 def route_path(name, params=None, **kwargs):
37 def route_path(name, params=None, **kwargs):
39 import urllib
38 import urllib
40 from rhodecode.apps._base import ADMIN_PREFIX
39 from rhodecode.apps._base import ADMIN_PREFIX
41
40
42 base_url = {
41 base_url = {
43 'users':
42 'users':
44 ADMIN_PREFIX + '/users',
43 ADMIN_PREFIX + '/users',
45 'users_data':
44 'users_data':
46 ADMIN_PREFIX + '/users_data',
45 ADMIN_PREFIX + '/users_data',
47 'users_create':
46 'users_create':
48 ADMIN_PREFIX + '/users/create',
47 ADMIN_PREFIX + '/users/create',
49 'users_new':
48 'users_new':
50 ADMIN_PREFIX + '/users/new',
49 ADMIN_PREFIX + '/users/new',
51 'user_edit':
50 'user_edit':
52 ADMIN_PREFIX + '/users/{user_id}/edit',
51 ADMIN_PREFIX + '/users/{user_id}/edit',
53 'user_edit_advanced':
52 'user_edit_advanced':
54 ADMIN_PREFIX + '/users/{user_id}/edit/advanced',
53 ADMIN_PREFIX + '/users/{user_id}/edit/advanced',
55 'user_edit_global_perms':
54 'user_edit_global_perms':
56 ADMIN_PREFIX + '/users/{user_id}/edit/global_permissions',
55 ADMIN_PREFIX + '/users/{user_id}/edit/global_permissions',
57 'user_edit_global_perms_update':
56 'user_edit_global_perms_update':
58 ADMIN_PREFIX + '/users/{user_id}/edit/global_permissions/update',
57 ADMIN_PREFIX + '/users/{user_id}/edit/global_permissions/update',
59 'user_update':
58 'user_update':
60 ADMIN_PREFIX + '/users/{user_id}/update',
59 ADMIN_PREFIX + '/users/{user_id}/update',
61 'user_delete':
60 'user_delete':
62 ADMIN_PREFIX + '/users/{user_id}/delete',
61 ADMIN_PREFIX + '/users/{user_id}/delete',
63 'user_force_password_reset':
62 'user_force_password_reset':
64 ADMIN_PREFIX + '/users/{user_id}/password_reset',
63 ADMIN_PREFIX + '/users/{user_id}/password_reset',
65 'user_create_personal_repo_group':
64 'user_create_personal_repo_group':
66 ADMIN_PREFIX + '/users/{user_id}/create_repo_group',
65 ADMIN_PREFIX + '/users/{user_id}/create_repo_group',
67
66
68 'edit_user_auth_tokens':
67 'edit_user_auth_tokens':
69 ADMIN_PREFIX + '/users/{user_id}/edit/auth_tokens',
68 ADMIN_PREFIX + '/users/{user_id}/edit/auth_tokens',
70 'edit_user_auth_tokens_add':
69 'edit_user_auth_tokens_add':
71 ADMIN_PREFIX + '/users/{user_id}/edit/auth_tokens/new',
70 ADMIN_PREFIX + '/users/{user_id}/edit/auth_tokens/new',
72 'edit_user_auth_tokens_delete':
71 'edit_user_auth_tokens_delete':
73 ADMIN_PREFIX + '/users/{user_id}/edit/auth_tokens/delete',
72 ADMIN_PREFIX + '/users/{user_id}/edit/auth_tokens/delete',
74
73
75 'edit_user_emails':
74 'edit_user_emails':
76 ADMIN_PREFIX + '/users/{user_id}/edit/emails',
75 ADMIN_PREFIX + '/users/{user_id}/edit/emails',
77 'edit_user_emails_add':
76 'edit_user_emails_add':
78 ADMIN_PREFIX + '/users/{user_id}/edit/emails/new',
77 ADMIN_PREFIX + '/users/{user_id}/edit/emails/new',
79 'edit_user_emails_delete':
78 'edit_user_emails_delete':
80 ADMIN_PREFIX + '/users/{user_id}/edit/emails/delete',
79 ADMIN_PREFIX + '/users/{user_id}/edit/emails/delete',
81
80
82 'edit_user_ips':
81 'edit_user_ips':
83 ADMIN_PREFIX + '/users/{user_id}/edit/ips',
82 ADMIN_PREFIX + '/users/{user_id}/edit/ips',
84 'edit_user_ips_add':
83 'edit_user_ips_add':
85 ADMIN_PREFIX + '/users/{user_id}/edit/ips/new',
84 ADMIN_PREFIX + '/users/{user_id}/edit/ips/new',
86 'edit_user_ips_delete':
85 'edit_user_ips_delete':
87 ADMIN_PREFIX + '/users/{user_id}/edit/ips/delete',
86 ADMIN_PREFIX + '/users/{user_id}/edit/ips/delete',
88
87
89 'edit_user_perms_summary':
88 'edit_user_perms_summary':
90 ADMIN_PREFIX + '/users/{user_id}/edit/permissions_summary',
89 ADMIN_PREFIX + '/users/{user_id}/edit/permissions_summary',
91 'edit_user_perms_summary_json':
90 'edit_user_perms_summary_json':
92 ADMIN_PREFIX + '/users/{user_id}/edit/permissions_summary/json',
91 ADMIN_PREFIX + '/users/{user_id}/edit/permissions_summary/json',
93
92
94 'edit_user_audit_logs':
93 'edit_user_audit_logs':
95 ADMIN_PREFIX + '/users/{user_id}/edit/audit',
94 ADMIN_PREFIX + '/users/{user_id}/edit/audit',
96
95
97 }[name].format(**kwargs)
96 }[name].format(**kwargs)
98
97
99 if params:
98 if params:
100 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
99 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
101 return base_url
100 return base_url
102
101
103
102
104 class TestAdminUsersView(TestController):
103 class TestAdminUsersView(TestController):
105
104
106 def test_show_users(self):
105 def test_show_users(self):
107 self.log_user()
106 self.log_user()
108 self.app.get(route_path('users'))
107 self.app.get(route_path('users'))
109
108
110 def test_show_users_data(self, xhr_header):
109 def test_show_users_data(self, xhr_header):
111 self.log_user()
110 self.log_user()
112 response = self.app.get(route_path(
111 response = self.app.get(route_path(
113 'users_data'), extra_environ=xhr_header)
112 'users_data'), extra_environ=xhr_header)
114
113
115 all_users = User.query().filter(
114 all_users = User.query().filter(
116 User.username != User.DEFAULT_USER).count()
115 User.username != User.DEFAULT_USER).count()
117 assert response.json['recordsTotal'] == all_users
116 assert response.json['recordsTotal'] == all_users
118
117
119 def test_show_users_data_filtered(self, xhr_header):
118 def test_show_users_data_filtered(self, xhr_header):
120 self.log_user()
119 self.log_user()
121 response = self.app.get(route_path(
120 response = self.app.get(route_path(
122 'users_data', params={'search[value]': 'empty_search'}),
121 'users_data', params={'search[value]': 'empty_search'}),
123 extra_environ=xhr_header)
122 extra_environ=xhr_header)
124
123
125 all_users = User.query().filter(
124 all_users = User.query().filter(
126 User.username != User.DEFAULT_USER).count()
125 User.username != User.DEFAULT_USER).count()
127 assert response.json['recordsTotal'] == all_users
126 assert response.json['recordsTotal'] == all_users
128 assert response.json['recordsFiltered'] == 0
127 assert response.json['recordsFiltered'] == 0
129
128
130 def test_auth_tokens_default_user(self):
129 def test_auth_tokens_default_user(self):
131 self.log_user()
130 self.log_user()
132 user = User.get_default_user()
131 user = User.get_default_user()
133 response = self.app.get(
132 response = self.app.get(
134 route_path('edit_user_auth_tokens', user_id=user.user_id),
133 route_path('edit_user_auth_tokens', user_id=user.user_id),
135 status=302)
134 status=302)
136
135
137 def test_auth_tokens(self):
136 def test_auth_tokens(self):
138 self.log_user()
137 self.log_user()
139
138
140 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
139 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
141 response = self.app.get(
140 response = self.app.get(
142 route_path('edit_user_auth_tokens', user_id=user.user_id))
141 route_path('edit_user_auth_tokens', user_id=user.user_id))
143 for token in user.auth_tokens:
142 for token in user.auth_tokens:
144 response.mustcontain(token)
143 response.mustcontain(token)
145 response.mustcontain('never')
144 response.mustcontain('never')
146
145
147 @pytest.mark.parametrize("desc, lifetime", [
146 @pytest.mark.parametrize("desc, lifetime", [
148 ('forever', -1),
147 ('forever', -1),
149 ('5mins', 60*5),
148 ('5mins', 60*5),
150 ('30days', 60*60*24*30),
149 ('30days', 60*60*24*30),
151 ])
150 ])
152 def test_add_auth_token(self, desc, lifetime, user_util):
151 def test_add_auth_token(self, desc, lifetime, user_util):
153 self.log_user()
152 self.log_user()
154 user = user_util.create_user()
153 user = user_util.create_user()
155 user_id = user.user_id
154 user_id = user.user_id
156
155
157 response = self.app.post(
156 response = self.app.post(
158 route_path('edit_user_auth_tokens_add', user_id=user_id),
157 route_path('edit_user_auth_tokens_add', user_id=user_id),
159 {'description': desc, 'lifetime': lifetime,
158 {'description': desc, 'lifetime': lifetime,
160 'csrf_token': self.csrf_token})
159 'csrf_token': self.csrf_token})
161 assert_session_flash(response, 'Auth token successfully created')
160 assert_session_flash(response, 'Auth token successfully created')
162
161
163 response = response.follow()
162 response = response.follow()
164 user = User.get(user_id)
163 user = User.get(user_id)
165 for auth_token in user.auth_tokens:
164 for auth_token in user.auth_tokens:
166 response.mustcontain(auth_token)
165 response.mustcontain(auth_token)
167
166
168 def test_delete_auth_token(self, user_util):
167 def test_delete_auth_token(self, user_util):
169 self.log_user()
168 self.log_user()
170 user = user_util.create_user()
169 user = user_util.create_user()
171 user_id = user.user_id
170 user_id = user.user_id
172 keys = user.auth_tokens
171 keys = user.auth_tokens
173 assert 2 == len(keys)
172 assert 2 == len(keys)
174
173
175 response = self.app.post(
174 response = self.app.post(
176 route_path('edit_user_auth_tokens_add', user_id=user_id),
175 route_path('edit_user_auth_tokens_add', user_id=user_id),
177 {'description': 'desc', 'lifetime': -1,
176 {'description': 'desc', 'lifetime': -1,
178 'csrf_token': self.csrf_token})
177 'csrf_token': self.csrf_token})
179 assert_session_flash(response, 'Auth token successfully created')
178 assert_session_flash(response, 'Auth token successfully created')
180 response.follow()
179 response.follow()
181
180
182 # now delete our key
181 # now delete our key
183 keys = UserApiKeys.query().filter(UserApiKeys.user_id == user_id).all()
182 keys = UserApiKeys.query().filter(UserApiKeys.user_id == user_id).all()
184 assert 3 == len(keys)
183 assert 3 == len(keys)
185
184
186 response = self.app.post(
185 response = self.app.post(
187 route_path('edit_user_auth_tokens_delete', user_id=user_id),
186 route_path('edit_user_auth_tokens_delete', user_id=user_id),
188 {'del_auth_token': keys[0].user_api_key_id,
187 {'del_auth_token': keys[0].user_api_key_id,
189 'csrf_token': self.csrf_token})
188 'csrf_token': self.csrf_token})
190
189
191 assert_session_flash(response, 'Auth token successfully deleted')
190 assert_session_flash(response, 'Auth token successfully deleted')
192 keys = UserApiKeys.query().filter(UserApiKeys.user_id == user_id).all()
191 keys = UserApiKeys.query().filter(UserApiKeys.user_id == user_id).all()
193 assert 2 == len(keys)
192 assert 2 == len(keys)
194
193
195 def test_ips(self):
194 def test_ips(self):
196 self.log_user()
195 self.log_user()
197 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
196 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
198 response = self.app.get(route_path('edit_user_ips', user_id=user.user_id))
197 response = self.app.get(route_path('edit_user_ips', user_id=user.user_id))
199 response.mustcontain('All IP addresses are allowed')
198 response.mustcontain('All IP addresses are allowed')
200
199
201 @pytest.mark.parametrize("test_name, ip, ip_range, failure", [
200 @pytest.mark.parametrize("test_name, ip, ip_range, failure", [
202 ('127/24', '127.0.0.1/24', '127.0.0.0 - 127.0.0.255', False),
201 ('127/24', '127.0.0.1/24', '127.0.0.0 - 127.0.0.255', False),
203 ('10/32', '10.0.0.10/32', '10.0.0.10 - 10.0.0.10', False),
202 ('10/32', '10.0.0.10/32', '10.0.0.10 - 10.0.0.10', False),
204 ('0/16', '0.0.0.0/16', '0.0.0.0 - 0.0.255.255', False),
203 ('0/16', '0.0.0.0/16', '0.0.0.0 - 0.0.255.255', False),
205 ('0/8', '0.0.0.0/8', '0.0.0.0 - 0.255.255.255', False),
204 ('0/8', '0.0.0.0/8', '0.0.0.0 - 0.255.255.255', False),
206 ('127_bad_mask', '127.0.0.1/99', '127.0.0.1 - 127.0.0.1', True),
205 ('127_bad_mask', '127.0.0.1/99', '127.0.0.1 - 127.0.0.1', True),
207 ('127_bad_ip', 'foobar', 'foobar', True),
206 ('127_bad_ip', 'foobar', 'foobar', True),
208 ])
207 ])
209 def test_ips_add(self, user_util, test_name, ip, ip_range, failure):
208 def test_ips_add(self, user_util, test_name, ip, ip_range, failure):
210 self.log_user()
209 self.log_user()
211 user = user_util.create_user(username=test_name)
210 user = user_util.create_user(username=test_name)
212 user_id = user.user_id
211 user_id = user.user_id
213
212
214 response = self.app.post(
213 response = self.app.post(
215 route_path('edit_user_ips_add', user_id=user_id),
214 route_path('edit_user_ips_add', user_id=user_id),
216 params={'new_ip': ip, 'csrf_token': self.csrf_token})
215 params={'new_ip': ip, 'csrf_token': self.csrf_token})
217
216
218 if failure:
217 if failure:
219 assert_session_flash(
218 assert_session_flash(
220 response, 'Please enter a valid IPv4 or IpV6 address')
219 response, 'Please enter a valid IPv4 or IpV6 address')
221 response = self.app.get(route_path('edit_user_ips', user_id=user_id))
220 response = self.app.get(route_path('edit_user_ips', user_id=user_id))
222
221
223 response.mustcontain(no=[ip])
222 response.mustcontain(no=[ip])
224 response.mustcontain(no=[ip_range])
223 response.mustcontain(no=[ip_range])
225
224
226 else:
225 else:
227 response = self.app.get(route_path('edit_user_ips', user_id=user_id))
226 response = self.app.get(route_path('edit_user_ips', user_id=user_id))
228 response.mustcontain(ip)
227 response.mustcontain(ip)
229 response.mustcontain(ip_range)
228 response.mustcontain(ip_range)
230
229
231 def test_ips_delete(self, user_util):
230 def test_ips_delete(self, user_util):
232 self.log_user()
231 self.log_user()
233 user = user_util.create_user()
232 user = user_util.create_user()
234 user_id = user.user_id
233 user_id = user.user_id
235 ip = '127.0.0.1/32'
234 ip = '127.0.0.1/32'
236 ip_range = '127.0.0.1 - 127.0.0.1'
235 ip_range = '127.0.0.1 - 127.0.0.1'
237 new_ip = UserModel().add_extra_ip(user_id, ip)
236 new_ip = UserModel().add_extra_ip(user_id, ip)
238 Session().commit()
237 Session().commit()
239 new_ip_id = new_ip.ip_id
238 new_ip_id = new_ip.ip_id
240
239
241 response = self.app.get(route_path('edit_user_ips', user_id=user_id))
240 response = self.app.get(route_path('edit_user_ips', user_id=user_id))
242 response.mustcontain(ip)
241 response.mustcontain(ip)
243 response.mustcontain(ip_range)
242 response.mustcontain(ip_range)
244
243
245 self.app.post(
244 self.app.post(
246 route_path('edit_user_ips_delete', user_id=user_id),
245 route_path('edit_user_ips_delete', user_id=user_id),
247 params={'del_ip_id': new_ip_id, 'csrf_token': self.csrf_token})
246 params={'del_ip_id': new_ip_id, 'csrf_token': self.csrf_token})
248
247
249 response = self.app.get(route_path('edit_user_ips', user_id=user_id))
248 response = self.app.get(route_path('edit_user_ips', user_id=user_id))
250 response.mustcontain('All IP addresses are allowed')
249 response.mustcontain('All IP addresses are allowed')
251 response.mustcontain(no=[ip])
250 response.mustcontain(no=[ip])
252 response.mustcontain(no=[ip_range])
251 response.mustcontain(no=[ip_range])
253
252
254 def test_emails(self):
253 def test_emails(self):
255 self.log_user()
254 self.log_user()
256 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
255 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
257 response = self.app.get(
256 response = self.app.get(
258 route_path('edit_user_emails', user_id=user.user_id))
257 route_path('edit_user_emails', user_id=user.user_id))
259 response.mustcontain('No additional emails specified')
258 response.mustcontain('No additional emails specified')
260
259
261 def test_emails_add(self, user_util):
260 def test_emails_add(self, user_util):
262 self.log_user()
261 self.log_user()
263 user = user_util.create_user()
262 user = user_util.create_user()
264 user_id = user.user_id
263 user_id = user.user_id
265
264
266 self.app.post(
265 self.app.post(
267 route_path('edit_user_emails_add', user_id=user_id),
266 route_path('edit_user_emails_add', user_id=user_id),
268 params={'new_email': 'example@rhodecode.com',
267 params={'new_email': 'example@rhodecode.com',
269 'csrf_token': self.csrf_token})
268 'csrf_token': self.csrf_token})
270
269
271 response = self.app.get(
270 response = self.app.get(
272 route_path('edit_user_emails', user_id=user_id))
271 route_path('edit_user_emails', user_id=user_id))
273 response.mustcontain('example@rhodecode.com')
272 response.mustcontain('example@rhodecode.com')
274
273
275 def test_emails_add_existing_email(self, user_util, user_regular):
274 def test_emails_add_existing_email(self, user_util, user_regular):
276 existing_email = user_regular.email
275 existing_email = user_regular.email
277
276
278 self.log_user()
277 self.log_user()
279 user = user_util.create_user()
278 user = user_util.create_user()
280 user_id = user.user_id
279 user_id = user.user_id
281
280
282 response = self.app.post(
281 response = self.app.post(
283 route_path('edit_user_emails_add', user_id=user_id),
282 route_path('edit_user_emails_add', user_id=user_id),
284 params={'new_email': existing_email,
283 params={'new_email': existing_email,
285 'csrf_token': self.csrf_token})
284 'csrf_token': self.csrf_token})
286 assert_session_flash(
285 assert_session_flash(
287 response, 'This e-mail address is already taken')
286 response, 'This e-mail address is already taken')
288
287
289 response = self.app.get(
288 response = self.app.get(
290 route_path('edit_user_emails', user_id=user_id))
289 route_path('edit_user_emails', user_id=user_id))
291 response.mustcontain(no=[existing_email])
290 response.mustcontain(no=[existing_email])
292
291
293 def test_emails_delete(self, user_util):
292 def test_emails_delete(self, user_util):
294 self.log_user()
293 self.log_user()
295 user = user_util.create_user()
294 user = user_util.create_user()
296 user_id = user.user_id
295 user_id = user.user_id
297
296
298 self.app.post(
297 self.app.post(
299 route_path('edit_user_emails_add', user_id=user_id),
298 route_path('edit_user_emails_add', user_id=user_id),
300 params={'new_email': 'example@rhodecode.com',
299 params={'new_email': 'example@rhodecode.com',
301 'csrf_token': self.csrf_token})
300 'csrf_token': self.csrf_token})
302
301
303 response = self.app.get(
302 response = self.app.get(
304 route_path('edit_user_emails', user_id=user_id))
303 route_path('edit_user_emails', user_id=user_id))
305 response.mustcontain('example@rhodecode.com')
304 response.mustcontain('example@rhodecode.com')
306
305
307 user_email = UserEmailMap.query()\
306 user_email = UserEmailMap.query()\
308 .filter(UserEmailMap.email == 'example@rhodecode.com') \
307 .filter(UserEmailMap.email == 'example@rhodecode.com') \
309 .filter(UserEmailMap.user_id == user_id)\
308 .filter(UserEmailMap.user_id == user_id)\
310 .one()
309 .one()
311
310
312 del_email_id = user_email.email_id
311 del_email_id = user_email.email_id
313 self.app.post(
312 self.app.post(
314 route_path('edit_user_emails_delete', user_id=user_id),
313 route_path('edit_user_emails_delete', user_id=user_id),
315 params={'del_email_id': del_email_id,
314 params={'del_email_id': del_email_id,
316 'csrf_token': self.csrf_token})
315 'csrf_token': self.csrf_token})
317
316
318 response = self.app.get(
317 response = self.app.get(
319 route_path('edit_user_emails', user_id=user_id))
318 route_path('edit_user_emails', user_id=user_id))
320 response.mustcontain(no=['example@rhodecode.com'])
319 response.mustcontain(no=['example@rhodecode.com'])
321
320
322
321
323 def test_create(self, request, xhr_header):
322 def test_create(self, request, xhr_header):
324 self.log_user()
323 self.log_user()
325 username = 'newtestuser'
324 username = 'newtestuser'
326 password = 'test12'
325 password = 'test12'
327 password_confirmation = password
326 password_confirmation = password
328 name = 'name'
327 name = 'name'
329 lastname = 'lastname'
328 lastname = 'lastname'
330 email = 'mail@mail.com'
329 email = 'mail@mail.com'
331
330
332 self.app.get(route_path('users_new'))
331 self.app.get(route_path('users_new'))
333
332
334 response = self.app.post(route_path('users_create'), params={
333 response = self.app.post(route_path('users_create'), params={
335 'username': username,
334 'username': username,
336 'password': password,
335 'password': password,
337 'password_confirmation': password_confirmation,
336 'password_confirmation': password_confirmation,
338 'firstname': name,
337 'firstname': name,
339 'active': True,
338 'active': True,
340 'lastname': lastname,
339 'lastname': lastname,
341 'extern_name': 'rhodecode',
340 'extern_name': 'rhodecode',
342 'extern_type': 'rhodecode',
341 'extern_type': 'rhodecode',
343 'email': email,
342 'email': email,
344 'csrf_token': self.csrf_token,
343 'csrf_token': self.csrf_token,
345 })
344 })
346 user_link = h.link_to(
345 user_link = h.link_to(
347 username,
346 username,
348 route_path(
347 route_path(
349 'user_edit', user_id=User.get_by_username(username).user_id))
348 'user_edit', user_id=User.get_by_username(username).user_id))
350 assert_session_flash(response, 'Created user %s' % (user_link,))
349 assert_session_flash(response, 'Created user %s' % (user_link,))
351
350
352 @request.addfinalizer
351 @request.addfinalizer
353 def cleanup():
352 def cleanup():
354 fixture.destroy_user(username)
353 fixture.destroy_user(username)
355 Session().commit()
354 Session().commit()
356
355
357 new_user = User.query().filter(User.username == username).one()
356 new_user = User.query().filter(User.username == username).one()
358
357
359 assert new_user.username == username
358 assert new_user.username == username
360 assert auth.check_password(password, new_user.password)
359 assert auth.check_password(password, new_user.password)
361 assert new_user.name == name
360 assert new_user.name == name
362 assert new_user.lastname == lastname
361 assert new_user.lastname == lastname
363 assert new_user.email == email
362 assert new_user.email == email
364
363
365 response = self.app.get(route_path('users_data'),
364 response = self.app.get(route_path('users_data'),
366 extra_environ=xhr_header)
365 extra_environ=xhr_header)
367 response.mustcontain(username)
366 response.mustcontain(username)
368
367
369 def test_create_err(self):
368 def test_create_err(self):
370 self.log_user()
369 self.log_user()
371 username = 'new_user'
370 username = 'new_user'
372 password = ''
371 password = ''
373 name = 'name'
372 name = 'name'
374 lastname = 'lastname'
373 lastname = 'lastname'
375 email = 'errmail.com'
374 email = 'errmail.com'
376
375
377 self.app.get(route_path('users_new'))
376 self.app.get(route_path('users_new'))
378
377
379 response = self.app.post(route_path('users_create'), params={
378 response = self.app.post(route_path('users_create'), params={
380 'username': username,
379 'username': username,
381 'password': password,
380 'password': password,
382 'name': name,
381 'name': name,
383 'active': False,
382 'active': False,
384 'lastname': lastname,
383 'lastname': lastname,
385 'email': email,
384 'email': email,
386 'csrf_token': self.csrf_token,
385 'csrf_token': self.csrf_token,
387 })
386 })
388
387
389 msg = validators.ValidUsername(
388 msg = '???'
390 False, {})._messages['system_invalid_username']
391 msg = h.html_escape(msg % {'username': 'new_user'})
389 msg = h.html_escape(msg % {'username': 'new_user'})
392 response.mustcontain('<span class="error-message">%s</span>' % msg)
390 response.mustcontain('<span class="error-message">%s</span>' % msg)
393 response.mustcontain(
391 response.mustcontain(
394 '<span class="error-message">Please enter a value</span>')
392 '<span class="error-message">Please enter a value</span>')
395 response.mustcontain(
393 response.mustcontain(
396 '<span class="error-message">An email address must contain a'
394 '<span class="error-message">An email address must contain a'
397 ' single @</span>')
395 ' single @</span>')
398
396
399 def get_user():
397 def get_user():
400 Session().query(User).filter(User.username == username).one()
398 Session().query(User).filter(User.username == username).one()
401
399
402 with pytest.raises(NoResultFound):
400 with pytest.raises(NoResultFound):
403 get_user()
401 get_user()
404
402
405 def test_new(self):
403 def test_new(self):
406 self.log_user()
404 self.log_user()
407 self.app.get(route_path('users_new'))
405 self.app.get(route_path('users_new'))
408
406
409 @pytest.mark.parametrize("name, attrs", [
407 @pytest.mark.parametrize("name, attrs", [
410 ('firstname', {'firstname': 'new_username'}),
408 ('firstname', {'firstname': 'new_username'}),
411 ('lastname', {'lastname': 'new_username'}),
409 ('lastname', {'lastname': 'new_username'}),
412 ('admin', {'admin': True}),
410 ('admin', {'admin': True}),
413 ('admin', {'admin': False}),
411 ('admin', {'admin': False}),
414 ('extern_type', {'extern_type': 'ldap'}),
412 ('extern_type', {'extern_type': 'ldap'}),
415 ('extern_type', {'extern_type': None}),
413 ('extern_type', {'extern_type': None}),
416 ('extern_name', {'extern_name': 'test'}),
414 ('extern_name', {'extern_name': 'test'}),
417 ('extern_name', {'extern_name': None}),
415 ('extern_name', {'extern_name': None}),
418 ('active', {'active': False}),
416 ('active', {'active': False}),
419 ('active', {'active': True}),
417 ('active', {'active': True}),
420 ('email', {'email': 'some@email.com'}),
418 ('email', {'email': 'some@email.com'}),
421 ('language', {'language': 'de'}),
419 ('language', {'language': 'de'}),
422 ('language', {'language': 'en'}),
420 ('language', {'language': 'en'}),
423 # ('new_password', {'new_password': 'foobar123',
421 # ('new_password', {'new_password': 'foobar123',
424 # 'password_confirmation': 'foobar123'})
422 # 'password_confirmation': 'foobar123'})
425 ])
423 ])
426 def test_update(self, name, attrs, user_util):
424 def test_update(self, name, attrs, user_util):
427 self.log_user()
425 self.log_user()
428 usr = user_util.create_user(
426 usr = user_util.create_user(
429 password='qweqwe',
427 password='qweqwe',
430 email='testme@rhodecode.org',
428 email='testme@rhodecode.org',
431 extern_type='rhodecode',
429 extern_type='rhodecode',
432 extern_name='xxx',
430 extern_name='xxx',
433 )
431 )
434 user_id = usr.user_id
432 user_id = usr.user_id
435 Session().commit()
433 Session().commit()
436
434
437 params = usr.get_api_data()
435 params = usr.get_api_data()
438 cur_lang = params['language'] or 'en'
436 cur_lang = params['language'] or 'en'
439 params.update({
437 params.update({
440 'password_confirmation': '',
438 'password_confirmation': '',
441 'new_password': '',
439 'new_password': '',
442 'language': cur_lang,
440 'language': cur_lang,
443 'csrf_token': self.csrf_token,
441 'csrf_token': self.csrf_token,
444 })
442 })
445 params.update({'new_password': ''})
443 params.update({'new_password': ''})
446 params.update(attrs)
444 params.update(attrs)
447 if name == 'email':
445 if name == 'email':
448 params['emails'] = [attrs['email']]
446 params['emails'] = [attrs['email']]
449 elif name == 'extern_type':
447 elif name == 'extern_type':
450 # cannot update this via form, expected value is original one
448 # cannot update this via form, expected value is original one
451 params['extern_type'] = "rhodecode"
449 params['extern_type'] = "rhodecode"
452 elif name == 'extern_name':
450 elif name == 'extern_name':
453 # cannot update this via form, expected value is original one
451 # cannot update this via form, expected value is original one
454 params['extern_name'] = 'xxx'
452 params['extern_name'] = 'xxx'
455 # special case since this user is not
453 # special case since this user is not
456 # logged in yet his data is not filled
454 # logged in yet his data is not filled
457 # so we use creation data
455 # so we use creation data
458
456
459 response = self.app.post(
457 response = self.app.post(
460 route_path('user_update', user_id=usr.user_id), params)
458 route_path('user_update', user_id=usr.user_id), params)
461 assert response.status_int == 302
459 assert response.status_int == 302
462 assert_session_flash(response, 'User updated successfully')
460 assert_session_flash(response, 'User updated successfully')
463
461
464 updated_user = User.get(user_id)
462 updated_user = User.get(user_id)
465 updated_params = updated_user.get_api_data()
463 updated_params = updated_user.get_api_data()
466 updated_params.update({'password_confirmation': ''})
464 updated_params.update({'password_confirmation': ''})
467 updated_params.update({'new_password': ''})
465 updated_params.update({'new_password': ''})
468
466
469 del params['csrf_token']
467 del params['csrf_token']
470 assert params == updated_params
468 assert params == updated_params
471
469
472 def test_update_and_migrate_password(
470 def test_update_and_migrate_password(
473 self, autologin_user, real_crypto_backend, user_util):
471 self, autologin_user, real_crypto_backend, user_util):
474
472
475 user = user_util.create_user()
473 user = user_util.create_user()
476 temp_user = user.username
474 temp_user = user.username
477 user.password = auth._RhodeCodeCryptoSha256().hash_create(
475 user.password = auth._RhodeCodeCryptoSha256().hash_create(
478 b'test123')
476 b'test123')
479 Session().add(user)
477 Session().add(user)
480 Session().commit()
478 Session().commit()
481
479
482 params = user.get_api_data()
480 params = user.get_api_data()
483
481
484 params.update({
482 params.update({
485 'password_confirmation': 'qweqwe123',
483 'password_confirmation': 'qweqwe123',
486 'new_password': 'qweqwe123',
484 'new_password': 'qweqwe123',
487 'language': 'en',
485 'language': 'en',
488 'csrf_token': autologin_user.csrf_token,
486 'csrf_token': autologin_user.csrf_token,
489 })
487 })
490
488
491 response = self.app.post(
489 response = self.app.post(
492 route_path('user_update', user_id=user.user_id), params)
490 route_path('user_update', user_id=user.user_id), params)
493 assert response.status_int == 302
491 assert response.status_int == 302
494 assert_session_flash(response, 'User updated successfully')
492 assert_session_flash(response, 'User updated successfully')
495
493
496 # new password should be bcrypted, after log-in and transfer
494 # new password should be bcrypted, after log-in and transfer
497 user = User.get_by_username(temp_user)
495 user = User.get_by_username(temp_user)
498 assert user.password.startswith('$')
496 assert user.password.startswith('$')
499
497
500 updated_user = User.get_by_username(temp_user)
498 updated_user = User.get_by_username(temp_user)
501 updated_params = updated_user.get_api_data()
499 updated_params = updated_user.get_api_data()
502 updated_params.update({'password_confirmation': 'qweqwe123'})
500 updated_params.update({'password_confirmation': 'qweqwe123'})
503 updated_params.update({'new_password': 'qweqwe123'})
501 updated_params.update({'new_password': 'qweqwe123'})
504
502
505 del params['csrf_token']
503 del params['csrf_token']
506 assert params == updated_params
504 assert params == updated_params
507
505
508 def test_delete(self):
506 def test_delete(self):
509 self.log_user()
507 self.log_user()
510 username = 'newtestuserdeleteme'
508 username = 'newtestuserdeleteme'
511
509
512 fixture.create_user(name=username)
510 fixture.create_user(name=username)
513
511
514 new_user = Session().query(User)\
512 new_user = Session().query(User)\
515 .filter(User.username == username).one()
513 .filter(User.username == username).one()
516 response = self.app.post(
514 response = self.app.post(
517 route_path('user_delete', user_id=new_user.user_id),
515 route_path('user_delete', user_id=new_user.user_id),
518 params={'csrf_token': self.csrf_token})
516 params={'csrf_token': self.csrf_token})
519
517
520 assert_session_flash(response, 'Successfully deleted user')
518 assert_session_flash(response, 'Successfully deleted user')
521
519
522 def test_delete_owner_of_repository(self, request, user_util):
520 def test_delete_owner_of_repository(self, request, user_util):
523 self.log_user()
521 self.log_user()
524 obj_name = 'test_repo'
522 obj_name = 'test_repo'
525 usr = user_util.create_user()
523 usr = user_util.create_user()
526 username = usr.username
524 username = usr.username
527 fixture.create_repo(obj_name, cur_user=usr.username)
525 fixture.create_repo(obj_name, cur_user=usr.username)
528
526
529 new_user = Session().query(User)\
527 new_user = Session().query(User)\
530 .filter(User.username == username).one()
528 .filter(User.username == username).one()
531 response = self.app.post(
529 response = self.app.post(
532 route_path('user_delete', user_id=new_user.user_id),
530 route_path('user_delete', user_id=new_user.user_id),
533 params={'csrf_token': self.csrf_token})
531 params={'csrf_token': self.csrf_token})
534
532
535 msg = 'user "%s" still owns 1 repositories and cannot be removed. ' \
533 msg = 'user "%s" still owns 1 repositories and cannot be removed. ' \
536 'Switch owners or remove those repositories:%s' % (username,
534 'Switch owners or remove those repositories:%s' % (username,
537 obj_name)
535 obj_name)
538 assert_session_flash(response, msg)
536 assert_session_flash(response, msg)
539 fixture.destroy_repo(obj_name)
537 fixture.destroy_repo(obj_name)
540
538
541 def test_delete_owner_of_repository_detaching(self, request, user_util):
539 def test_delete_owner_of_repository_detaching(self, request, user_util):
542 self.log_user()
540 self.log_user()
543 obj_name = 'test_repo'
541 obj_name = 'test_repo'
544 usr = user_util.create_user(auto_cleanup=False)
542 usr = user_util.create_user(auto_cleanup=False)
545 username = usr.username
543 username = usr.username
546 fixture.create_repo(obj_name, cur_user=usr.username)
544 fixture.create_repo(obj_name, cur_user=usr.username)
547
545
548 new_user = Session().query(User)\
546 new_user = Session().query(User)\
549 .filter(User.username == username).one()
547 .filter(User.username == username).one()
550 response = self.app.post(
548 response = self.app.post(
551 route_path('user_delete', user_id=new_user.user_id),
549 route_path('user_delete', user_id=new_user.user_id),
552 params={'user_repos': 'detach', 'csrf_token': self.csrf_token})
550 params={'user_repos': 'detach', 'csrf_token': self.csrf_token})
553
551
554 msg = 'Detached 1 repositories'
552 msg = 'Detached 1 repositories'
555 assert_session_flash(response, msg)
553 assert_session_flash(response, msg)
556 fixture.destroy_repo(obj_name)
554 fixture.destroy_repo(obj_name)
557
555
558 def test_delete_owner_of_repository_deleting(self, request, user_util):
556 def test_delete_owner_of_repository_deleting(self, request, user_util):
559 self.log_user()
557 self.log_user()
560 obj_name = 'test_repo'
558 obj_name = 'test_repo'
561 usr = user_util.create_user(auto_cleanup=False)
559 usr = user_util.create_user(auto_cleanup=False)
562 username = usr.username
560 username = usr.username
563 fixture.create_repo(obj_name, cur_user=usr.username)
561 fixture.create_repo(obj_name, cur_user=usr.username)
564
562
565 new_user = Session().query(User)\
563 new_user = Session().query(User)\
566 .filter(User.username == username).one()
564 .filter(User.username == username).one()
567 response = self.app.post(
565 response = self.app.post(
568 route_path('user_delete', user_id=new_user.user_id),
566 route_path('user_delete', user_id=new_user.user_id),
569 params={'user_repos': 'delete', 'csrf_token': self.csrf_token})
567 params={'user_repos': 'delete', 'csrf_token': self.csrf_token})
570
568
571 msg = 'Deleted 1 repositories'
569 msg = 'Deleted 1 repositories'
572 assert_session_flash(response, msg)
570 assert_session_flash(response, msg)
573
571
574 def test_delete_owner_of_repository_group(self, request, user_util):
572 def test_delete_owner_of_repository_group(self, request, user_util):
575 self.log_user()
573 self.log_user()
576 obj_name = 'test_group'
574 obj_name = 'test_group'
577 usr = user_util.create_user()
575 usr = user_util.create_user()
578 username = usr.username
576 username = usr.username
579 fixture.create_repo_group(obj_name, cur_user=usr.username)
577 fixture.create_repo_group(obj_name, cur_user=usr.username)
580
578
581 new_user = Session().query(User)\
579 new_user = Session().query(User)\
582 .filter(User.username == username).one()
580 .filter(User.username == username).one()
583 response = self.app.post(
581 response = self.app.post(
584 route_path('user_delete', user_id=new_user.user_id),
582 route_path('user_delete', user_id=new_user.user_id),
585 params={'csrf_token': self.csrf_token})
583 params={'csrf_token': self.csrf_token})
586
584
587 msg = 'user "%s" still owns 1 repository groups and cannot be removed. ' \
585 msg = 'user "%s" still owns 1 repository groups and cannot be removed. ' \
588 'Switch owners or remove those repository groups:%s' % (username,
586 'Switch owners or remove those repository groups:%s' % (username,
589 obj_name)
587 obj_name)
590 assert_session_flash(response, msg)
588 assert_session_flash(response, msg)
591 fixture.destroy_repo_group(obj_name)
589 fixture.destroy_repo_group(obj_name)
592
590
593 def test_delete_owner_of_repository_group_detaching(self, request, user_util):
591 def test_delete_owner_of_repository_group_detaching(self, request, user_util):
594 self.log_user()
592 self.log_user()
595 obj_name = 'test_group'
593 obj_name = 'test_group'
596 usr = user_util.create_user(auto_cleanup=False)
594 usr = user_util.create_user(auto_cleanup=False)
597 username = usr.username
595 username = usr.username
598 fixture.create_repo_group(obj_name, cur_user=usr.username)
596 fixture.create_repo_group(obj_name, cur_user=usr.username)
599
597
600 new_user = Session().query(User)\
598 new_user = Session().query(User)\
601 .filter(User.username == username).one()
599 .filter(User.username == username).one()
602 response = self.app.post(
600 response = self.app.post(
603 route_path('user_delete', user_id=new_user.user_id),
601 route_path('user_delete', user_id=new_user.user_id),
604 params={'user_repo_groups': 'delete', 'csrf_token': self.csrf_token})
602 params={'user_repo_groups': 'delete', 'csrf_token': self.csrf_token})
605
603
606 msg = 'Deleted 1 repository groups'
604 msg = 'Deleted 1 repository groups'
607 assert_session_flash(response, msg)
605 assert_session_flash(response, msg)
608
606
609 def test_delete_owner_of_repository_group_deleting(self, request, user_util):
607 def test_delete_owner_of_repository_group_deleting(self, request, user_util):
610 self.log_user()
608 self.log_user()
611 obj_name = 'test_group'
609 obj_name = 'test_group'
612 usr = user_util.create_user(auto_cleanup=False)
610 usr = user_util.create_user(auto_cleanup=False)
613 username = usr.username
611 username = usr.username
614 fixture.create_repo_group(obj_name, cur_user=usr.username)
612 fixture.create_repo_group(obj_name, cur_user=usr.username)
615
613
616 new_user = Session().query(User)\
614 new_user = Session().query(User)\
617 .filter(User.username == username).one()
615 .filter(User.username == username).one()
618 response = self.app.post(
616 response = self.app.post(
619 route_path('user_delete', user_id=new_user.user_id),
617 route_path('user_delete', user_id=new_user.user_id),
620 params={'user_repo_groups': 'detach', 'csrf_token': self.csrf_token})
618 params={'user_repo_groups': 'detach', 'csrf_token': self.csrf_token})
621
619
622 msg = 'Detached 1 repository groups'
620 msg = 'Detached 1 repository groups'
623 assert_session_flash(response, msg)
621 assert_session_flash(response, msg)
624 fixture.destroy_repo_group(obj_name)
622 fixture.destroy_repo_group(obj_name)
625
623
626 def test_delete_owner_of_user_group(self, request, user_util):
624 def test_delete_owner_of_user_group(self, request, user_util):
627 self.log_user()
625 self.log_user()
628 obj_name = 'test_user_group'
626 obj_name = 'test_user_group'
629 usr = user_util.create_user()
627 usr = user_util.create_user()
630 username = usr.username
628 username = usr.username
631 fixture.create_user_group(obj_name, cur_user=usr.username)
629 fixture.create_user_group(obj_name, cur_user=usr.username)
632
630
633 new_user = Session().query(User)\
631 new_user = Session().query(User)\
634 .filter(User.username == username).one()
632 .filter(User.username == username).one()
635 response = self.app.post(
633 response = self.app.post(
636 route_path('user_delete', user_id=new_user.user_id),
634 route_path('user_delete', user_id=new_user.user_id),
637 params={'csrf_token': self.csrf_token})
635 params={'csrf_token': self.csrf_token})
638
636
639 msg = 'user "%s" still owns 1 user groups and cannot be removed. ' \
637 msg = 'user "%s" still owns 1 user groups and cannot be removed. ' \
640 'Switch owners or remove those user groups:%s' % (username,
638 'Switch owners or remove those user groups:%s' % (username,
641 obj_name)
639 obj_name)
642 assert_session_flash(response, msg)
640 assert_session_flash(response, msg)
643 fixture.destroy_user_group(obj_name)
641 fixture.destroy_user_group(obj_name)
644
642
645 def test_delete_owner_of_user_group_detaching(self, request, user_util):
643 def test_delete_owner_of_user_group_detaching(self, request, user_util):
646 self.log_user()
644 self.log_user()
647 obj_name = 'test_user_group'
645 obj_name = 'test_user_group'
648 usr = user_util.create_user(auto_cleanup=False)
646 usr = user_util.create_user(auto_cleanup=False)
649 username = usr.username
647 username = usr.username
650 fixture.create_user_group(obj_name, cur_user=usr.username)
648 fixture.create_user_group(obj_name, cur_user=usr.username)
651
649
652 new_user = Session().query(User)\
650 new_user = Session().query(User)\
653 .filter(User.username == username).one()
651 .filter(User.username == username).one()
654 try:
652 try:
655 response = self.app.post(
653 response = self.app.post(
656 route_path('user_delete', user_id=new_user.user_id),
654 route_path('user_delete', user_id=new_user.user_id),
657 params={'user_user_groups': 'detach',
655 params={'user_user_groups': 'detach',
658 'csrf_token': self.csrf_token})
656 'csrf_token': self.csrf_token})
659
657
660 msg = 'Detached 1 user groups'
658 msg = 'Detached 1 user groups'
661 assert_session_flash(response, msg)
659 assert_session_flash(response, msg)
662 finally:
660 finally:
663 fixture.destroy_user_group(obj_name)
661 fixture.destroy_user_group(obj_name)
664
662
665 def test_delete_owner_of_user_group_deleting(self, request, user_util):
663 def test_delete_owner_of_user_group_deleting(self, request, user_util):
666 self.log_user()
664 self.log_user()
667 obj_name = 'test_user_group'
665 obj_name = 'test_user_group'
668 usr = user_util.create_user(auto_cleanup=False)
666 usr = user_util.create_user(auto_cleanup=False)
669 username = usr.username
667 username = usr.username
670 fixture.create_user_group(obj_name, cur_user=usr.username)
668 fixture.create_user_group(obj_name, cur_user=usr.username)
671
669
672 new_user = Session().query(User)\
670 new_user = Session().query(User)\
673 .filter(User.username == username).one()
671 .filter(User.username == username).one()
674 response = self.app.post(
672 response = self.app.post(
675 route_path('user_delete', user_id=new_user.user_id),
673 route_path('user_delete', user_id=new_user.user_id),
676 params={'user_user_groups': 'delete', 'csrf_token': self.csrf_token})
674 params={'user_user_groups': 'delete', 'csrf_token': self.csrf_token})
677
675
678 msg = 'Deleted 1 user groups'
676 msg = 'Deleted 1 user groups'
679 assert_session_flash(response, msg)
677 assert_session_flash(response, msg)
680
678
681 def test_edit(self, user_util):
679 def test_edit(self, user_util):
682 self.log_user()
680 self.log_user()
683 user = user_util.create_user()
681 user = user_util.create_user()
684 self.app.get(route_path('user_edit', user_id=user.user_id))
682 self.app.get(route_path('user_edit', user_id=user.user_id))
685
683
686 def test_edit_default_user_redirect(self):
684 def test_edit_default_user_redirect(self):
687 self.log_user()
685 self.log_user()
688 user = User.get_default_user()
686 user = User.get_default_user()
689 self.app.get(route_path('user_edit', user_id=user.user_id), status=302)
687 self.app.get(route_path('user_edit', user_id=user.user_id), status=302)
690
688
691 @pytest.mark.parametrize(
689 @pytest.mark.parametrize(
692 'repo_create, repo_create_write, user_group_create, repo_group_create,'
690 'repo_create, repo_create_write, user_group_create, repo_group_create,'
693 'fork_create, inherit_default_permissions, expect_error,'
691 'fork_create, inherit_default_permissions, expect_error,'
694 'expect_form_error', [
692 'expect_form_error', [
695 ('hg.create.none', 'hg.create.write_on_repogroup.false',
693 ('hg.create.none', 'hg.create.write_on_repogroup.false',
696 'hg.usergroup.create.false', 'hg.repogroup.create.false',
694 'hg.usergroup.create.false', 'hg.repogroup.create.false',
697 'hg.fork.none', 'hg.inherit_default_perms.false', False, False),
695 'hg.fork.none', 'hg.inherit_default_perms.false', False, False),
698 ('hg.create.repository', 'hg.create.write_on_repogroup.false',
696 ('hg.create.repository', 'hg.create.write_on_repogroup.false',
699 'hg.usergroup.create.false', 'hg.repogroup.create.false',
697 'hg.usergroup.create.false', 'hg.repogroup.create.false',
700 'hg.fork.none', 'hg.inherit_default_perms.false', False, False),
698 'hg.fork.none', 'hg.inherit_default_perms.false', False, False),
701 ('hg.create.repository', 'hg.create.write_on_repogroup.true',
699 ('hg.create.repository', 'hg.create.write_on_repogroup.true',
702 'hg.usergroup.create.true', 'hg.repogroup.create.true',
700 'hg.usergroup.create.true', 'hg.repogroup.create.true',
703 'hg.fork.repository', 'hg.inherit_default_perms.false', False,
701 'hg.fork.repository', 'hg.inherit_default_perms.false', False,
704 False),
702 False),
705 ('hg.create.XXX', 'hg.create.write_on_repogroup.true',
703 ('hg.create.XXX', 'hg.create.write_on_repogroup.true',
706 'hg.usergroup.create.true', 'hg.repogroup.create.true',
704 'hg.usergroup.create.true', 'hg.repogroup.create.true',
707 'hg.fork.repository', 'hg.inherit_default_perms.false', False,
705 'hg.fork.repository', 'hg.inherit_default_perms.false', False,
708 True),
706 True),
709 ('', '', '', '', '', '', True, False),
707 ('', '', '', '', '', '', True, False),
710 ])
708 ])
711 def test_global_perms_on_user(
709 def test_global_perms_on_user(
712 self, repo_create, repo_create_write, user_group_create,
710 self, repo_create, repo_create_write, user_group_create,
713 repo_group_create, fork_create, expect_error, expect_form_error,
711 repo_group_create, fork_create, expect_error, expect_form_error,
714 inherit_default_permissions, user_util):
712 inherit_default_permissions, user_util):
715 self.log_user()
713 self.log_user()
716 user = user_util.create_user()
714 user = user_util.create_user()
717 uid = user.user_id
715 uid = user.user_id
718
716
719 # ENABLE REPO CREATE ON A GROUP
717 # ENABLE REPO CREATE ON A GROUP
720 perm_params = {
718 perm_params = {
721 'inherit_default_permissions': False,
719 'inherit_default_permissions': False,
722 'default_repo_create': repo_create,
720 'default_repo_create': repo_create,
723 'default_repo_create_on_write': repo_create_write,
721 'default_repo_create_on_write': repo_create_write,
724 'default_user_group_create': user_group_create,
722 'default_user_group_create': user_group_create,
725 'default_repo_group_create': repo_group_create,
723 'default_repo_group_create': repo_group_create,
726 'default_fork_create': fork_create,
724 'default_fork_create': fork_create,
727 'default_inherit_default_permissions': inherit_default_permissions,
725 'default_inherit_default_permissions': inherit_default_permissions,
728 'csrf_token': self.csrf_token,
726 'csrf_token': self.csrf_token,
729 }
727 }
730 response = self.app.post(
728 response = self.app.post(
731 route_path('user_edit_global_perms_update', user_id=uid),
729 route_path('user_edit_global_perms_update', user_id=uid),
732 params=perm_params)
730 params=perm_params)
733
731
734 if expect_form_error:
732 if expect_form_error:
735 assert response.status_int == 200
733 assert response.status_int == 200
736 response.mustcontain('Value must be one of')
734 response.mustcontain('Value must be one of')
737 else:
735 else:
738 if expect_error:
736 if expect_error:
739 msg = 'An error occurred during permissions saving'
737 msg = 'An error occurred during permissions saving'
740 else:
738 else:
741 msg = 'User global permissions updated successfully'
739 msg = 'User global permissions updated successfully'
742 ug = User.get(uid)
740 ug = User.get(uid)
743 del perm_params['inherit_default_permissions']
741 del perm_params['inherit_default_permissions']
744 del perm_params['csrf_token']
742 del perm_params['csrf_token']
745 assert perm_params == ug.get_default_perms()
743 assert perm_params == ug.get_default_perms()
746 assert_session_flash(response, msg)
744 assert_session_flash(response, msg)
747
745
748 def test_global_permissions_initial_values(self, user_util):
746 def test_global_permissions_initial_values(self, user_util):
749 self.log_user()
747 self.log_user()
750 user = user_util.create_user()
748 user = user_util.create_user()
751 uid = user.user_id
749 uid = user.user_id
752 response = self.app.get(
750 response = self.app.get(
753 route_path('user_edit_global_perms', user_id=uid))
751 route_path('user_edit_global_perms', user_id=uid))
754 default_user = User.get_default_user()
752 default_user = User.get_default_user()
755 default_permissions = default_user.get_default_perms()
753 default_permissions = default_user.get_default_perms()
756 assert_response = response.assert_response()
754 assert_response = response.assert_response()
757 expected_permissions = (
755 expected_permissions = (
758 'default_repo_create', 'default_repo_create_on_write',
756 'default_repo_create', 'default_repo_create_on_write',
759 'default_fork_create', 'default_repo_group_create',
757 'default_fork_create', 'default_repo_group_create',
760 'default_user_group_create', 'default_inherit_default_permissions')
758 'default_user_group_create', 'default_inherit_default_permissions')
761 for permission in expected_permissions:
759 for permission in expected_permissions:
762 css_selector = '[name={}][checked=checked]'.format(permission)
760 css_selector = '[name={}][checked=checked]'.format(permission)
763 element = assert_response.get_element(css_selector)
761 element = assert_response.get_element(css_selector)
764 assert element.value == default_permissions[permission]
762 assert element.value == default_permissions[permission]
765
763
766 def test_perms_summary_page(self):
764 def test_perms_summary_page(self):
767 user = self.log_user()
765 user = self.log_user()
768 response = self.app.get(
766 response = self.app.get(
769 route_path('edit_user_perms_summary', user_id=user['user_id']))
767 route_path('edit_user_perms_summary', user_id=user['user_id']))
770 for repo in Repository.query().all():
768 for repo in Repository.query().all():
771 response.mustcontain(repo.repo_name)
769 response.mustcontain(repo.repo_name)
772
770
773 def test_perms_summary_page_json(self):
771 def test_perms_summary_page_json(self):
774 user = self.log_user()
772 user = self.log_user()
775 response = self.app.get(
773 response = self.app.get(
776 route_path('edit_user_perms_summary_json', user_id=user['user_id']))
774 route_path('edit_user_perms_summary_json', user_id=user['user_id']))
777 for repo in Repository.query().all():
775 for repo in Repository.query().all():
778 response.mustcontain(repo.repo_name)
776 response.mustcontain(repo.repo_name)
779
777
780 def test_audit_log_page(self):
778 def test_audit_log_page(self):
781 user = self.log_user()
779 user = self.log_user()
782 self.app.get(
780 self.app.get(
783 route_path('edit_user_audit_logs', user_id=user['user_id']))
781 route_path('edit_user_audit_logs', user_id=user['user_id']))
@@ -1,91 +1,90 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22
22
23 from pyramid.httpexceptions import HTTPNotFound
23 from pyramid.httpexceptions import HTTPNotFound
24 from pyramid.view import view_config
24 from pyramid.view import view_config
25
25
26 from rhodecode.apps._base import BaseAppView
26 from rhodecode.apps._base import BaseAppView
27 from rhodecode.model.db import joinedload, UserLog
27 from rhodecode.model.db import joinedload, UserLog
28 from rhodecode.lib.user_log_filter import user_log_filter
28 from rhodecode.lib.user_log_filter import user_log_filter
29 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
29 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
30 from rhodecode.lib.utils2 import safe_int
30 from rhodecode.lib.utils2 import safe_int
31 from rhodecode.lib.helpers import Page
31 from rhodecode.lib.helpers import Page
32
32
33 log = logging.getLogger(__name__)
33 log = logging.getLogger(__name__)
34
34
35
35
36 class AdminAuditLogsView(BaseAppView):
36 class AdminAuditLogsView(BaseAppView):
37 def load_default_context(self):
37 def load_default_context(self):
38 c = self._get_local_tmpl_context()
38 c = self._get_local_tmpl_context()
39 self._register_global_c(c)
40 return c
39 return c
41
40
42 @LoginRequired()
41 @LoginRequired()
43 @HasPermissionAllDecorator('hg.admin')
42 @HasPermissionAllDecorator('hg.admin')
44 @view_config(
43 @view_config(
45 route_name='admin_audit_logs', request_method='GET',
44 route_name='admin_audit_logs', request_method='GET',
46 renderer='rhodecode:templates/admin/admin_audit_logs.mako')
45 renderer='rhodecode:templates/admin/admin_audit_logs.mako')
47 def admin_audit_logs(self):
46 def admin_audit_logs(self):
48 c = self.load_default_context()
47 c = self.load_default_context()
49
48
50 users_log = UserLog.query()\
49 users_log = UserLog.query()\
51 .options(joinedload(UserLog.user))\
50 .options(joinedload(UserLog.user))\
52 .options(joinedload(UserLog.repository))
51 .options(joinedload(UserLog.repository))
53
52
54 # FILTERING
53 # FILTERING
55 c.search_term = self.request.GET.get('filter')
54 c.search_term = self.request.GET.get('filter')
56 try:
55 try:
57 users_log = user_log_filter(users_log, c.search_term)
56 users_log = user_log_filter(users_log, c.search_term)
58 except Exception:
57 except Exception:
59 # we want this to crash for now
58 # we want this to crash for now
60 raise
59 raise
61
60
62 users_log = users_log.order_by(UserLog.action_date.desc())
61 users_log = users_log.order_by(UserLog.action_date.desc())
63
62
64 p = safe_int(self.request.GET.get('page', 1), 1)
63 p = safe_int(self.request.GET.get('page', 1), 1)
65
64
66 def url_generator(**kw):
65 def url_generator(**kw):
67 if c.search_term:
66 if c.search_term:
68 kw['filter'] = c.search_term
67 kw['filter'] = c.search_term
69 return self.request.current_route_path(_query=kw)
68 return self.request.current_route_path(_query=kw)
70
69
71 c.audit_logs = Page(users_log, page=p, items_per_page=10,
70 c.audit_logs = Page(users_log, page=p, items_per_page=10,
72 url=url_generator)
71 url=url_generator)
73 return self._get_template_context(c)
72 return self._get_template_context(c)
74
73
75 @LoginRequired()
74 @LoginRequired()
76 @HasPermissionAllDecorator('hg.admin')
75 @HasPermissionAllDecorator('hg.admin')
77 @view_config(
76 @view_config(
78 route_name='admin_audit_log_entry', request_method='GET',
77 route_name='admin_audit_log_entry', request_method='GET',
79 renderer='rhodecode:templates/admin/admin_audit_log_entry.mako')
78 renderer='rhodecode:templates/admin/admin_audit_log_entry.mako')
80 def admin_audit_log_entry(self):
79 def admin_audit_log_entry(self):
81 c = self.load_default_context()
80 c = self.load_default_context()
82 audit_log_id = self.request.matchdict['audit_log_id']
81 audit_log_id = self.request.matchdict['audit_log_id']
83
82
84 c.audit_log_entry = UserLog.query()\
83 c.audit_log_entry = UserLog.query()\
85 .options(joinedload(UserLog.user))\
84 .options(joinedload(UserLog.user))\
86 .options(joinedload(UserLog.repository))\
85 .options(joinedload(UserLog.repository))\
87 .filter(UserLog.user_log_id == audit_log_id).scalar()
86 .filter(UserLog.user_log_id == audit_log_id).scalar()
88 if not c.audit_log_entry:
87 if not c.audit_log_entry:
89 raise HTTPNotFound()
88 raise HTTPNotFound()
90
89
91 return self._get_template_context(c)
90 return self._get_template_context(c)
@@ -1,111 +1,111 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22
22
23 import formencode
23 import formencode
24 import formencode.htmlfill
24 import formencode.htmlfill
25
25
26 from pyramid.view import view_config
26 from pyramid.view import view_config
27 from pyramid.httpexceptions import HTTPFound
27 from pyramid.httpexceptions import HTTPFound
28 from pyramid.renderers import render
28 from pyramid.renderers import render
29 from pyramid.response import Response
29 from pyramid.response import Response
30
30
31 from rhodecode.apps._base import BaseAppView
31 from rhodecode.apps._base import BaseAppView
32 from rhodecode.lib.auth import (
32 from rhodecode.lib.auth import (
33 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
33 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
34 from rhodecode.lib import helpers as h
34 from rhodecode.lib import helpers as h
35 from rhodecode.model.forms import DefaultsForm
35 from rhodecode.model.forms import DefaultsForm
36 from rhodecode.model.meta import Session
36 from rhodecode.model.meta import Session
37 from rhodecode import BACKENDS
37 from rhodecode import BACKENDS
38 from rhodecode.model.settings import SettingsModel
38 from rhodecode.model.settings import SettingsModel
39
39
40 log = logging.getLogger(__name__)
40 log = logging.getLogger(__name__)
41
41
42
42
43 class AdminDefaultSettingsView(BaseAppView):
43 class AdminDefaultSettingsView(BaseAppView):
44 def load_default_context(self):
44 def load_default_context(self):
45 c = self._get_local_tmpl_context()
45 c = self._get_local_tmpl_context()
46
46
47 self._register_global_c(c)
47
48 return c
48 return c
49
49
50 @LoginRequired()
50 @LoginRequired()
51 @HasPermissionAllDecorator('hg.admin')
51 @HasPermissionAllDecorator('hg.admin')
52 @view_config(
52 @view_config(
53 route_name='admin_defaults_repositories', request_method='GET',
53 route_name='admin_defaults_repositories', request_method='GET',
54 renderer='rhodecode:templates/admin/defaults/defaults.mako')
54 renderer='rhodecode:templates/admin/defaults/defaults.mako')
55 def defaults_repository_show(self):
55 def defaults_repository_show(self):
56 c = self.load_default_context()
56 c = self.load_default_context()
57 c.backends = BACKENDS.keys()
57 c.backends = BACKENDS.keys()
58 c.active = 'repositories'
58 c.active = 'repositories'
59 defaults = SettingsModel().get_default_repo_settings()
59 defaults = SettingsModel().get_default_repo_settings()
60
60
61 data = render(
61 data = render(
62 'rhodecode:templates/admin/defaults/defaults.mako',
62 'rhodecode:templates/admin/defaults/defaults.mako',
63 self._get_template_context(c), self.request)
63 self._get_template_context(c), self.request)
64 html = formencode.htmlfill.render(
64 html = formencode.htmlfill.render(
65 data,
65 data,
66 defaults=defaults,
66 defaults=defaults,
67 encoding="UTF-8",
67 encoding="UTF-8",
68 force_defaults=False
68 force_defaults=False
69 )
69 )
70 return Response(html)
70 return Response(html)
71
71
72 @LoginRequired()
72 @LoginRequired()
73 @HasPermissionAllDecorator('hg.admin')
73 @HasPermissionAllDecorator('hg.admin')
74 @CSRFRequired()
74 @CSRFRequired()
75 @view_config(
75 @view_config(
76 route_name='admin_defaults_repositories_update', request_method='POST',
76 route_name='admin_defaults_repositories_update', request_method='POST',
77 renderer='rhodecode:templates/admin/defaults/defaults.mako')
77 renderer='rhodecode:templates/admin/defaults/defaults.mako')
78 def defaults_repository_update(self):
78 def defaults_repository_update(self):
79 _ = self.request.translate
79 _ = self.request.translate
80 c = self.load_default_context()
80 c = self.load_default_context()
81 c.active = 'repositories'
81 c.active = 'repositories'
82 form = DefaultsForm()()
82 form = DefaultsForm(self.request.translate)()
83
83
84 try:
84 try:
85 form_result = form.to_python(dict(self.request.POST))
85 form_result = form.to_python(dict(self.request.POST))
86 for k, v in form_result.iteritems():
86 for k, v in form_result.iteritems():
87 setting = SettingsModel().create_or_update_setting(k, v)
87 setting = SettingsModel().create_or_update_setting(k, v)
88 Session().add(setting)
88 Session().add(setting)
89 Session().commit()
89 Session().commit()
90 h.flash(_('Default settings updated successfully'),
90 h.flash(_('Default settings updated successfully'),
91 category='success')
91 category='success')
92
92
93 except formencode.Invalid as errors:
93 except formencode.Invalid as errors:
94 data = render(
94 data = render(
95 'rhodecode:templates/admin/defaults/defaults.mako',
95 'rhodecode:templates/admin/defaults/defaults.mako',
96 self._get_template_context(c), self.request)
96 self._get_template_context(c), self.request)
97 html = formencode.htmlfill.render(
97 html = formencode.htmlfill.render(
98 data,
98 data,
99 defaults=errors.value,
99 defaults=errors.value,
100 errors=errors.error_dict or {},
100 errors=errors.error_dict or {},
101 prefix_error=False,
101 prefix_error=False,
102 encoding="UTF-8",
102 encoding="UTF-8",
103 force_defaults=False
103 force_defaults=False
104 )
104 )
105 return Response(html)
105 return Response(html)
106 except Exception:
106 except Exception:
107 log.exception('Exception in update action')
107 log.exception('Exception in update action')
108 h.flash(_('Error occurred during update of default values'),
108 h.flash(_('Error occurred during update of default values'),
109 category='error')
109 category='error')
110
110
111 raise HTTPFound(h.route_path('admin_defaults_repositories'))
111 raise HTTPFound(h.route_path('admin_defaults_repositories'))
@@ -1,53 +1,53 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import collections
21 import collections
22 import logging
22 import logging
23
23
24 from pyramid.view import view_config
24 from pyramid.view import view_config
25
25
26 from rhodecode.apps._base import BaseAppView
26 from rhodecode.apps._base import BaseAppView
27 from rhodecode.apps.admin.navigation import navigation_list
27 from rhodecode.apps.admin.navigation import navigation_list
28 from rhodecode.lib.auth import (LoginRequired, HasPermissionAllDecorator)
28 from rhodecode.lib.auth import (LoginRequired, HasPermissionAllDecorator)
29 from rhodecode.lib.utils import read_opensource_licenses
29 from rhodecode.lib.utils import read_opensource_licenses
30
30
31 log = logging.getLogger(__name__)
31 log = logging.getLogger(__name__)
32
32
33
33
34 class OpenSourceLicensesAdminSettingsView(BaseAppView):
34 class OpenSourceLicensesAdminSettingsView(BaseAppView):
35
35
36 def load_default_context(self):
36 def load_default_context(self):
37 c = self._get_local_tmpl_context()
37 c = self._get_local_tmpl_context()
38 self._register_global_c(c)
38
39 return c
39 return c
40
40
41 @LoginRequired()
41 @LoginRequired()
42 @HasPermissionAllDecorator('hg.admin')
42 @HasPermissionAllDecorator('hg.admin')
43 @view_config(
43 @view_config(
44 route_name='admin_settings_open_source', request_method='GET',
44 route_name='admin_settings_open_source', request_method='GET',
45 renderer='rhodecode:templates/admin/settings/settings.mako')
45 renderer='rhodecode:templates/admin/settings/settings.mako')
46 def open_source_licenses(self):
46 def open_source_licenses(self):
47 c = self.load_default_context()
47 c = self.load_default_context()
48 c.active = 'open_source'
48 c.active = 'open_source'
49 c.navlist = navigation_list(self.request)
49 c.navlist = navigation_list(self.request)
50 items = sorted(read_opensource_licenses().items(), key=lambda t: t[0])
50 items = sorted(read_opensource_licenses().items(), key=lambda t: t[0])
51 c.opensource_licenses = collections.OrderedDict(items)
51 c.opensource_licenses = collections.OrderedDict(items)
52
52
53 return self._get_template_context(c)
53 return self._get_template_context(c)
@@ -1,482 +1,486 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import re
21 import re
22 import logging
22 import logging
23 import formencode
23 import formencode
24 import formencode.htmlfill
24 import formencode.htmlfill
25 import datetime
25 import datetime
26 from pyramid.interfaces import IRoutesMapper
26 from pyramid.interfaces import IRoutesMapper
27
27
28 from pyramid.view import view_config
28 from pyramid.view import view_config
29 from pyramid.httpexceptions import HTTPFound
29 from pyramid.httpexceptions import HTTPFound
30 from pyramid.renderers import render
30 from pyramid.renderers import render
31 from pyramid.response import Response
31 from pyramid.response import Response
32
32
33 from rhodecode.apps._base import BaseAppView, DataGridAppView
33 from rhodecode.apps._base import BaseAppView, DataGridAppView
34 from rhodecode.apps.ssh_support import SshKeyFileChangeEvent
34 from rhodecode.apps.ssh_support import SshKeyFileChangeEvent
35 from rhodecode.events import trigger
35 from rhodecode.events import trigger
36
36
37 from rhodecode.lib import helpers as h
37 from rhodecode.lib import helpers as h
38 from rhodecode.lib.auth import (
38 from rhodecode.lib.auth import (
39 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
39 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
40 from rhodecode.lib.utils2 import aslist, safe_unicode
40 from rhodecode.lib.utils2 import aslist, safe_unicode
41 from rhodecode.model.db import (
41 from rhodecode.model.db import (
42 or_, coalesce, User, UserIpMap, UserSshKeys)
42 or_, coalesce, User, UserIpMap, UserSshKeys)
43 from rhodecode.model.forms import (
43 from rhodecode.model.forms import (
44 ApplicationPermissionsForm, ObjectPermissionsForm, UserPermissionsForm)
44 ApplicationPermissionsForm, ObjectPermissionsForm, UserPermissionsForm)
45 from rhodecode.model.meta import Session
45 from rhodecode.model.meta import Session
46 from rhodecode.model.permission import PermissionModel
46 from rhodecode.model.permission import PermissionModel
47 from rhodecode.model.settings import SettingsModel
47 from rhodecode.model.settings import SettingsModel
48
48
49
49
50 log = logging.getLogger(__name__)
50 log = logging.getLogger(__name__)
51
51
52
52
53 class AdminPermissionsView(BaseAppView, DataGridAppView):
53 class AdminPermissionsView(BaseAppView, DataGridAppView):
54 def load_default_context(self):
54 def load_default_context(self):
55 c = self._get_local_tmpl_context()
55 c = self._get_local_tmpl_context()
56
56
57 self._register_global_c(c)
57
58 PermissionModel().set_global_permission_choices(
58 PermissionModel().set_global_permission_choices(
59 c, gettext_translator=self.request.translate)
59 c, gettext_translator=self.request.translate)
60 return c
60 return c
61
61
62 @LoginRequired()
62 @LoginRequired()
63 @HasPermissionAllDecorator('hg.admin')
63 @HasPermissionAllDecorator('hg.admin')
64 @view_config(
64 @view_config(
65 route_name='admin_permissions_application', request_method='GET',
65 route_name='admin_permissions_application', request_method='GET',
66 renderer='rhodecode:templates/admin/permissions/permissions.mako')
66 renderer='rhodecode:templates/admin/permissions/permissions.mako')
67 def permissions_application(self):
67 def permissions_application(self):
68 c = self.load_default_context()
68 c = self.load_default_context()
69 c.active = 'application'
69 c.active = 'application'
70
70
71 c.user = User.get_default_user(refresh=True)
71 c.user = User.get_default_user(refresh=True)
72
72
73 app_settings = SettingsModel().get_all_settings()
73 app_settings = SettingsModel().get_all_settings()
74 defaults = {
74 defaults = {
75 'anonymous': c.user.active,
75 'anonymous': c.user.active,
76 'default_register_message': app_settings.get(
76 'default_register_message': app_settings.get(
77 'rhodecode_register_message')
77 'rhodecode_register_message')
78 }
78 }
79 defaults.update(c.user.get_default_perms())
79 defaults.update(c.user.get_default_perms())
80
80
81 data = render('rhodecode:templates/admin/permissions/permissions.mako',
81 data = render('rhodecode:templates/admin/permissions/permissions.mako',
82 self._get_template_context(c), self.request)
82 self._get_template_context(c), self.request)
83 html = formencode.htmlfill.render(
83 html = formencode.htmlfill.render(
84 data,
84 data,
85 defaults=defaults,
85 defaults=defaults,
86 encoding="UTF-8",
86 encoding="UTF-8",
87 force_defaults=False
87 force_defaults=False
88 )
88 )
89 return Response(html)
89 return Response(html)
90
90
91 @LoginRequired()
91 @LoginRequired()
92 @HasPermissionAllDecorator('hg.admin')
92 @HasPermissionAllDecorator('hg.admin')
93 @CSRFRequired()
93 @CSRFRequired()
94 @view_config(
94 @view_config(
95 route_name='admin_permissions_application_update', request_method='POST',
95 route_name='admin_permissions_application_update', request_method='POST',
96 renderer='rhodecode:templates/admin/permissions/permissions.mako')
96 renderer='rhodecode:templates/admin/permissions/permissions.mako')
97 def permissions_application_update(self):
97 def permissions_application_update(self):
98 _ = self.request.translate
98 _ = self.request.translate
99 c = self.load_default_context()
99 c = self.load_default_context()
100 c.active = 'application'
100 c.active = 'application'
101
101
102 _form = ApplicationPermissionsForm(
102 _form = ApplicationPermissionsForm(
103 self.request.translate,
103 [x[0] for x in c.register_choices],
104 [x[0] for x in c.register_choices],
104 [x[0] for x in c.password_reset_choices],
105 [x[0] for x in c.password_reset_choices],
105 [x[0] for x in c.extern_activate_choices])()
106 [x[0] for x in c.extern_activate_choices])()
106
107
107 try:
108 try:
108 form_result = _form.to_python(dict(self.request.POST))
109 form_result = _form.to_python(dict(self.request.POST))
109 form_result.update({'perm_user_name': User.DEFAULT_USER})
110 form_result.update({'perm_user_name': User.DEFAULT_USER})
110 PermissionModel().update_application_permissions(form_result)
111 PermissionModel().update_application_permissions(form_result)
111
112
112 settings = [
113 settings = [
113 ('register_message', 'default_register_message'),
114 ('register_message', 'default_register_message'),
114 ]
115 ]
115 for setting, form_key in settings:
116 for setting, form_key in settings:
116 sett = SettingsModel().create_or_update_setting(
117 sett = SettingsModel().create_or_update_setting(
117 setting, form_result[form_key])
118 setting, form_result[form_key])
118 Session().add(sett)
119 Session().add(sett)
119
120
120 Session().commit()
121 Session().commit()
121 h.flash(_('Application permissions updated successfully'),
122 h.flash(_('Application permissions updated successfully'),
122 category='success')
123 category='success')
123
124
124 except formencode.Invalid as errors:
125 except formencode.Invalid as errors:
125 defaults = errors.value
126 defaults = errors.value
126
127
127 data = render(
128 data = render(
128 'rhodecode:templates/admin/permissions/permissions.mako',
129 'rhodecode:templates/admin/permissions/permissions.mako',
129 self._get_template_context(c), self.request)
130 self._get_template_context(c), self.request)
130 html = formencode.htmlfill.render(
131 html = formencode.htmlfill.render(
131 data,
132 data,
132 defaults=defaults,
133 defaults=defaults,
133 errors=errors.error_dict or {},
134 errors=errors.error_dict or {},
134 prefix_error=False,
135 prefix_error=False,
135 encoding="UTF-8",
136 encoding="UTF-8",
136 force_defaults=False
137 force_defaults=False
137 )
138 )
138 return Response(html)
139 return Response(html)
139
140
140 except Exception:
141 except Exception:
141 log.exception("Exception during update of permissions")
142 log.exception("Exception during update of permissions")
142 h.flash(_('Error occurred during update of permissions'),
143 h.flash(_('Error occurred during update of permissions'),
143 category='error')
144 category='error')
144
145
145 raise HTTPFound(h.route_path('admin_permissions_application'))
146 raise HTTPFound(h.route_path('admin_permissions_application'))
146
147
147 @LoginRequired()
148 @LoginRequired()
148 @HasPermissionAllDecorator('hg.admin')
149 @HasPermissionAllDecorator('hg.admin')
149 @view_config(
150 @view_config(
150 route_name='admin_permissions_object', request_method='GET',
151 route_name='admin_permissions_object', request_method='GET',
151 renderer='rhodecode:templates/admin/permissions/permissions.mako')
152 renderer='rhodecode:templates/admin/permissions/permissions.mako')
152 def permissions_objects(self):
153 def permissions_objects(self):
153 c = self.load_default_context()
154 c = self.load_default_context()
154 c.active = 'objects'
155 c.active = 'objects'
155
156
156 c.user = User.get_default_user(refresh=True)
157 c.user = User.get_default_user(refresh=True)
157 defaults = {}
158 defaults = {}
158 defaults.update(c.user.get_default_perms())
159 defaults.update(c.user.get_default_perms())
159
160
160 data = render(
161 data = render(
161 'rhodecode:templates/admin/permissions/permissions.mako',
162 'rhodecode:templates/admin/permissions/permissions.mako',
162 self._get_template_context(c), self.request)
163 self._get_template_context(c), self.request)
163 html = formencode.htmlfill.render(
164 html = formencode.htmlfill.render(
164 data,
165 data,
165 defaults=defaults,
166 defaults=defaults,
166 encoding="UTF-8",
167 encoding="UTF-8",
167 force_defaults=False
168 force_defaults=False
168 )
169 )
169 return Response(html)
170 return Response(html)
170
171
171 @LoginRequired()
172 @LoginRequired()
172 @HasPermissionAllDecorator('hg.admin')
173 @HasPermissionAllDecorator('hg.admin')
173 @CSRFRequired()
174 @CSRFRequired()
174 @view_config(
175 @view_config(
175 route_name='admin_permissions_object_update', request_method='POST',
176 route_name='admin_permissions_object_update', request_method='POST',
176 renderer='rhodecode:templates/admin/permissions/permissions.mako')
177 renderer='rhodecode:templates/admin/permissions/permissions.mako')
177 def permissions_objects_update(self):
178 def permissions_objects_update(self):
178 _ = self.request.translate
179 _ = self.request.translate
179 c = self.load_default_context()
180 c = self.load_default_context()
180 c.active = 'objects'
181 c.active = 'objects'
181
182
182 _form = ObjectPermissionsForm(
183 _form = ObjectPermissionsForm(
184 self.request.translate,
183 [x[0] for x in c.repo_perms_choices],
185 [x[0] for x in c.repo_perms_choices],
184 [x[0] for x in c.group_perms_choices],
186 [x[0] for x in c.group_perms_choices],
185 [x[0] for x in c.user_group_perms_choices])()
187 [x[0] for x in c.user_group_perms_choices])()
186
188
187 try:
189 try:
188 form_result = _form.to_python(dict(self.request.POST))
190 form_result = _form.to_python(dict(self.request.POST))
189 form_result.update({'perm_user_name': User.DEFAULT_USER})
191 form_result.update({'perm_user_name': User.DEFAULT_USER})
190 PermissionModel().update_object_permissions(form_result)
192 PermissionModel().update_object_permissions(form_result)
191
193
192 Session().commit()
194 Session().commit()
193 h.flash(_('Object permissions updated successfully'),
195 h.flash(_('Object permissions updated successfully'),
194 category='success')
196 category='success')
195
197
196 except formencode.Invalid as errors:
198 except formencode.Invalid as errors:
197 defaults = errors.value
199 defaults = errors.value
198
200
199 data = render(
201 data = render(
200 'rhodecode:templates/admin/permissions/permissions.mako',
202 'rhodecode:templates/admin/permissions/permissions.mako',
201 self._get_template_context(c), self.request)
203 self._get_template_context(c), self.request)
202 html = formencode.htmlfill.render(
204 html = formencode.htmlfill.render(
203 data,
205 data,
204 defaults=defaults,
206 defaults=defaults,
205 errors=errors.error_dict or {},
207 errors=errors.error_dict or {},
206 prefix_error=False,
208 prefix_error=False,
207 encoding="UTF-8",
209 encoding="UTF-8",
208 force_defaults=False
210 force_defaults=False
209 )
211 )
210 return Response(html)
212 return Response(html)
211 except Exception:
213 except Exception:
212 log.exception("Exception during update of permissions")
214 log.exception("Exception during update of permissions")
213 h.flash(_('Error occurred during update of permissions'),
215 h.flash(_('Error occurred during update of permissions'),
214 category='error')
216 category='error')
215
217
216 raise HTTPFound(h.route_path('admin_permissions_object'))
218 raise HTTPFound(h.route_path('admin_permissions_object'))
217
219
218 @LoginRequired()
220 @LoginRequired()
219 @HasPermissionAllDecorator('hg.admin')
221 @HasPermissionAllDecorator('hg.admin')
220 @view_config(
222 @view_config(
221 route_name='admin_permissions_global', request_method='GET',
223 route_name='admin_permissions_global', request_method='GET',
222 renderer='rhodecode:templates/admin/permissions/permissions.mako')
224 renderer='rhodecode:templates/admin/permissions/permissions.mako')
223 def permissions_global(self):
225 def permissions_global(self):
224 c = self.load_default_context()
226 c = self.load_default_context()
225 c.active = 'global'
227 c.active = 'global'
226
228
227 c.user = User.get_default_user(refresh=True)
229 c.user = User.get_default_user(refresh=True)
228 defaults = {}
230 defaults = {}
229 defaults.update(c.user.get_default_perms())
231 defaults.update(c.user.get_default_perms())
230
232
231 data = render(
233 data = render(
232 'rhodecode:templates/admin/permissions/permissions.mako',
234 'rhodecode:templates/admin/permissions/permissions.mako',
233 self._get_template_context(c), self.request)
235 self._get_template_context(c), self.request)
234 html = formencode.htmlfill.render(
236 html = formencode.htmlfill.render(
235 data,
237 data,
236 defaults=defaults,
238 defaults=defaults,
237 encoding="UTF-8",
239 encoding="UTF-8",
238 force_defaults=False
240 force_defaults=False
239 )
241 )
240 return Response(html)
242 return Response(html)
241
243
242 @LoginRequired()
244 @LoginRequired()
243 @HasPermissionAllDecorator('hg.admin')
245 @HasPermissionAllDecorator('hg.admin')
244 @CSRFRequired()
246 @CSRFRequired()
245 @view_config(
247 @view_config(
246 route_name='admin_permissions_global_update', request_method='POST',
248 route_name='admin_permissions_global_update', request_method='POST',
247 renderer='rhodecode:templates/admin/permissions/permissions.mako')
249 renderer='rhodecode:templates/admin/permissions/permissions.mako')
248 def permissions_global_update(self):
250 def permissions_global_update(self):
249 _ = self.request.translate
251 _ = self.request.translate
250 c = self.load_default_context()
252 c = self.load_default_context()
251 c.active = 'global'
253 c.active = 'global'
252
254
253 _form = UserPermissionsForm(
255 _form = UserPermissionsForm(
256 self.request.translate,
254 [x[0] for x in c.repo_create_choices],
257 [x[0] for x in c.repo_create_choices],
255 [x[0] for x in c.repo_create_on_write_choices],
258 [x[0] for x in c.repo_create_on_write_choices],
256 [x[0] for x in c.repo_group_create_choices],
259 [x[0] for x in c.repo_group_create_choices],
257 [x[0] for x in c.user_group_create_choices],
260 [x[0] for x in c.user_group_create_choices],
258 [x[0] for x in c.fork_choices],
261 [x[0] for x in c.fork_choices],
259 [x[0] for x in c.inherit_default_permission_choices])()
262 [x[0] for x in c.inherit_default_permission_choices])()
260
263
261 try:
264 try:
262 form_result = _form.to_python(dict(self.request.POST))
265 form_result = _form.to_python(dict(self.request.POST))
263 form_result.update({'perm_user_name': User.DEFAULT_USER})
266 form_result.update({'perm_user_name': User.DEFAULT_USER})
264 PermissionModel().update_user_permissions(form_result)
267 PermissionModel().update_user_permissions(form_result)
265
268
266 Session().commit()
269 Session().commit()
267 h.flash(_('Global permissions updated successfully'),
270 h.flash(_('Global permissions updated successfully'),
268 category='success')
271 category='success')
269
272
270 except formencode.Invalid as errors:
273 except formencode.Invalid as errors:
271 defaults = errors.value
274 defaults = errors.value
272
275
273 data = render(
276 data = render(
274 'rhodecode:templates/admin/permissions/permissions.mako',
277 'rhodecode:templates/admin/permissions/permissions.mako',
275 self._get_template_context(c), self.request)
278 self._get_template_context(c), self.request)
276 html = formencode.htmlfill.render(
279 html = formencode.htmlfill.render(
277 data,
280 data,
278 defaults=defaults,
281 defaults=defaults,
279 errors=errors.error_dict or {},
282 errors=errors.error_dict or {},
280 prefix_error=False,
283 prefix_error=False,
281 encoding="UTF-8",
284 encoding="UTF-8",
282 force_defaults=False
285 force_defaults=False
283 )
286 )
284 return Response(html)
287 return Response(html)
285 except Exception:
288 except Exception:
286 log.exception("Exception during update of permissions")
289 log.exception("Exception during update of permissions")
287 h.flash(_('Error occurred during update of permissions'),
290 h.flash(_('Error occurred during update of permissions'),
288 category='error')
291 category='error')
289
292
290 raise HTTPFound(h.route_path('admin_permissions_global'))
293 raise HTTPFound(h.route_path('admin_permissions_global'))
291
294
292 @LoginRequired()
295 @LoginRequired()
293 @HasPermissionAllDecorator('hg.admin')
296 @HasPermissionAllDecorator('hg.admin')
294 @view_config(
297 @view_config(
295 route_name='admin_permissions_ips', request_method='GET',
298 route_name='admin_permissions_ips', request_method='GET',
296 renderer='rhodecode:templates/admin/permissions/permissions.mako')
299 renderer='rhodecode:templates/admin/permissions/permissions.mako')
297 def permissions_ips(self):
300 def permissions_ips(self):
298 c = self.load_default_context()
301 c = self.load_default_context()
299 c.active = 'ips'
302 c.active = 'ips'
300
303
301 c.user = User.get_default_user(refresh=True)
304 c.user = User.get_default_user(refresh=True)
302 c.user_ip_map = (
305 c.user_ip_map = (
303 UserIpMap.query().filter(UserIpMap.user == c.user).all())
306 UserIpMap.query().filter(UserIpMap.user == c.user).all())
304
307
305 return self._get_template_context(c)
308 return self._get_template_context(c)
306
309
307 @LoginRequired()
310 @LoginRequired()
308 @HasPermissionAllDecorator('hg.admin')
311 @HasPermissionAllDecorator('hg.admin')
309 @view_config(
312 @view_config(
310 route_name='admin_permissions_overview', request_method='GET',
313 route_name='admin_permissions_overview', request_method='GET',
311 renderer='rhodecode:templates/admin/permissions/permissions.mako')
314 renderer='rhodecode:templates/admin/permissions/permissions.mako')
312 def permissions_overview(self):
315 def permissions_overview(self):
313 c = self.load_default_context()
316 c = self.load_default_context()
314 c.active = 'perms'
317 c.active = 'perms'
315
318
316 c.user = User.get_default_user(refresh=True)
319 c.user = User.get_default_user(refresh=True)
317 c.perm_user = c.user.AuthUser()
320 c.perm_user = c.user.AuthUser()
318 return self._get_template_context(c)
321 return self._get_template_context(c)
319
322
320 @LoginRequired()
323 @LoginRequired()
321 @HasPermissionAllDecorator('hg.admin')
324 @HasPermissionAllDecorator('hg.admin')
322 @view_config(
325 @view_config(
323 route_name='admin_permissions_auth_token_access', request_method='GET',
326 route_name='admin_permissions_auth_token_access', request_method='GET',
324 renderer='rhodecode:templates/admin/permissions/permissions.mako')
327 renderer='rhodecode:templates/admin/permissions/permissions.mako')
325 def auth_token_access(self):
328 def auth_token_access(self):
326 from rhodecode import CONFIG
329 from rhodecode import CONFIG
327
330
328 c = self.load_default_context()
331 c = self.load_default_context()
329 c.active = 'auth_token_access'
332 c.active = 'auth_token_access'
330
333
331 c.user = User.get_default_user(refresh=True)
334 c.user = User.get_default_user(refresh=True)
332 c.perm_user = c.user.AuthUser()
335 c.perm_user = c.user.AuthUser()
333
336
334 mapper = self.request.registry.queryUtility(IRoutesMapper)
337 mapper = self.request.registry.queryUtility(IRoutesMapper)
335 c.view_data = []
338 c.view_data = []
336
339
337 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
340 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
338 introspector = self.request.registry.introspector
341 introspector = self.request.registry.introspector
339
342
340 view_intr = {}
343 view_intr = {}
341 for view_data in introspector.get_category('views'):
344 for view_data in introspector.get_category('views'):
342 intr = view_data['introspectable']
345 intr = view_data['introspectable']
343
346
344 if 'route_name' in intr and intr['attr']:
347 if 'route_name' in intr and intr['attr']:
345 view_intr[intr['route_name']] = '{}:{}'.format(
348 view_intr[intr['route_name']] = '{}:{}'.format(
346 str(intr['derived_callable'].func_name), intr['attr']
349 str(intr['derived_callable'].func_name), intr['attr']
347 )
350 )
348
351
349 c.whitelist_key = 'api_access_controllers_whitelist'
352 c.whitelist_key = 'api_access_controllers_whitelist'
350 c.whitelist_file = CONFIG.get('__file__')
353 c.whitelist_file = CONFIG.get('__file__')
351 whitelist_views = aslist(
354 whitelist_views = aslist(
352 CONFIG.get(c.whitelist_key), sep=',')
355 CONFIG.get(c.whitelist_key), sep=',')
353
356
354 for route_info in mapper.get_routes():
357 for route_info in mapper.get_routes():
355 if not route_info.name.startswith('__'):
358 if not route_info.name.startswith('__'):
356 routepath = route_info.pattern
359 routepath = route_info.pattern
357
360
358 def replace(matchobj):
361 def replace(matchobj):
359 if matchobj.group(1):
362 if matchobj.group(1):
360 return "{%s}" % matchobj.group(1).split(':')[0]
363 return "{%s}" % matchobj.group(1).split(':')[0]
361 else:
364 else:
362 return "{%s}" % matchobj.group(2)
365 return "{%s}" % matchobj.group(2)
363
366
364 routepath = _argument_prog.sub(replace, routepath)
367 routepath = _argument_prog.sub(replace, routepath)
365
368
366 if not routepath.startswith('/'):
369 if not routepath.startswith('/'):
367 routepath = '/' + routepath
370 routepath = '/' + routepath
368
371
369 view_fqn = view_intr.get(route_info.name, 'NOT AVAILABLE')
372 view_fqn = view_intr.get(route_info.name, 'NOT AVAILABLE')
370 active = view_fqn in whitelist_views
373 active = view_fqn in whitelist_views
371 c.view_data.append((route_info.name, view_fqn, routepath, active))
374 c.view_data.append((route_info.name, view_fqn, routepath, active))
372
375
373 c.whitelist_views = whitelist_views
376 c.whitelist_views = whitelist_views
374 return self._get_template_context(c)
377 return self._get_template_context(c)
375
378
376 def ssh_enabled(self):
379 def ssh_enabled(self):
377 return self.request.registry.settings.get(
380 return self.request.registry.settings.get(
378 'ssh.generate_authorized_keyfile')
381 'ssh.generate_authorized_keyfile')
379
382
380 @LoginRequired()
383 @LoginRequired()
381 @HasPermissionAllDecorator('hg.admin')
384 @HasPermissionAllDecorator('hg.admin')
382 @view_config(
385 @view_config(
383 route_name='admin_permissions_ssh_keys', request_method='GET',
386 route_name='admin_permissions_ssh_keys', request_method='GET',
384 renderer='rhodecode:templates/admin/permissions/permissions.mako')
387 renderer='rhodecode:templates/admin/permissions/permissions.mako')
385 def ssh_keys(self):
388 def ssh_keys(self):
386 c = self.load_default_context()
389 c = self.load_default_context()
387 c.active = 'ssh_keys'
390 c.active = 'ssh_keys'
388 c.ssh_enabled = self.ssh_enabled()
391 c.ssh_enabled = self.ssh_enabled()
389 return self._get_template_context(c)
392 return self._get_template_context(c)
390
393
391 @LoginRequired()
394 @LoginRequired()
392 @HasPermissionAllDecorator('hg.admin')
395 @HasPermissionAllDecorator('hg.admin')
393 @view_config(
396 @view_config(
394 route_name='admin_permissions_ssh_keys_data', request_method='GET',
397 route_name='admin_permissions_ssh_keys_data', request_method='GET',
395 renderer='json_ext', xhr=True)
398 renderer='json_ext', xhr=True)
396 def ssh_keys_data(self):
399 def ssh_keys_data(self):
397 _ = self.request.translate
400 _ = self.request.translate
401 self.load_default_context()
398 column_map = {
402 column_map = {
399 'fingerprint': 'ssh_key_fingerprint',
403 'fingerprint': 'ssh_key_fingerprint',
400 'username': User.username
404 'username': User.username
401 }
405 }
402 draw, start, limit = self._extract_chunk(self.request)
406 draw, start, limit = self._extract_chunk(self.request)
403 search_q, order_by, order_dir = self._extract_ordering(
407 search_q, order_by, order_dir = self._extract_ordering(
404 self.request, column_map=column_map)
408 self.request, column_map=column_map)
405
409
406 ssh_keys_data_total_count = UserSshKeys.query()\
410 ssh_keys_data_total_count = UserSshKeys.query()\
407 .count()
411 .count()
408
412
409 # json generate
413 # json generate
410 base_q = UserSshKeys.query().join(UserSshKeys.user)
414 base_q = UserSshKeys.query().join(UserSshKeys.user)
411
415
412 if search_q:
416 if search_q:
413 like_expression = u'%{}%'.format(safe_unicode(search_q))
417 like_expression = u'%{}%'.format(safe_unicode(search_q))
414 base_q = base_q.filter(or_(
418 base_q = base_q.filter(or_(
415 User.username.ilike(like_expression),
419 User.username.ilike(like_expression),
416 UserSshKeys.ssh_key_fingerprint.ilike(like_expression),
420 UserSshKeys.ssh_key_fingerprint.ilike(like_expression),
417 ))
421 ))
418
422
419 users_data_total_filtered_count = base_q.count()
423 users_data_total_filtered_count = base_q.count()
420
424
421 sort_col = self._get_order_col(order_by, UserSshKeys)
425 sort_col = self._get_order_col(order_by, UserSshKeys)
422 if sort_col:
426 if sort_col:
423 if order_dir == 'asc':
427 if order_dir == 'asc':
424 # handle null values properly to order by NULL last
428 # handle null values properly to order by NULL last
425 if order_by in ['created_on']:
429 if order_by in ['created_on']:
426 sort_col = coalesce(sort_col, datetime.date.max)
430 sort_col = coalesce(sort_col, datetime.date.max)
427 sort_col = sort_col.asc()
431 sort_col = sort_col.asc()
428 else:
432 else:
429 # handle null values properly to order by NULL last
433 # handle null values properly to order by NULL last
430 if order_by in ['created_on']:
434 if order_by in ['created_on']:
431 sort_col = coalesce(sort_col, datetime.date.min)
435 sort_col = coalesce(sort_col, datetime.date.min)
432 sort_col = sort_col.desc()
436 sort_col = sort_col.desc()
433
437
434 base_q = base_q.order_by(sort_col)
438 base_q = base_q.order_by(sort_col)
435 base_q = base_q.offset(start).limit(limit)
439 base_q = base_q.offset(start).limit(limit)
436
440
437 ssh_keys = base_q.all()
441 ssh_keys = base_q.all()
438
442
439 ssh_keys_data = []
443 ssh_keys_data = []
440 for ssh_key in ssh_keys:
444 for ssh_key in ssh_keys:
441 ssh_keys_data.append({
445 ssh_keys_data.append({
442 "username": h.gravatar_with_user(self.request, ssh_key.user.username),
446 "username": h.gravatar_with_user(self.request, ssh_key.user.username),
443 "fingerprint": ssh_key.ssh_key_fingerprint,
447 "fingerprint": ssh_key.ssh_key_fingerprint,
444 "description": ssh_key.description,
448 "description": ssh_key.description,
445 "created_on": h.format_date(ssh_key.created_on),
449 "created_on": h.format_date(ssh_key.created_on),
446 "accessed_on": h.format_date(ssh_key.accessed_on),
450 "accessed_on": h.format_date(ssh_key.accessed_on),
447 "action": h.link_to(
451 "action": h.link_to(
448 _('Edit'), h.route_path('edit_user_ssh_keys',
452 _('Edit'), h.route_path('edit_user_ssh_keys',
449 user_id=ssh_key.user.user_id))
453 user_id=ssh_key.user.user_id))
450 })
454 })
451
455
452 data = ({
456 data = ({
453 'draw': draw,
457 'draw': draw,
454 'data': ssh_keys_data,
458 'data': ssh_keys_data,
455 'recordsTotal': ssh_keys_data_total_count,
459 'recordsTotal': ssh_keys_data_total_count,
456 'recordsFiltered': users_data_total_filtered_count,
460 'recordsFiltered': users_data_total_filtered_count,
457 })
461 })
458
462
459 return data
463 return data
460
464
461 @LoginRequired()
465 @LoginRequired()
462 @HasPermissionAllDecorator('hg.admin')
466 @HasPermissionAllDecorator('hg.admin')
463 @CSRFRequired()
467 @CSRFRequired()
464 @view_config(
468 @view_config(
465 route_name='admin_permissions_ssh_keys_update', request_method='POST',
469 route_name='admin_permissions_ssh_keys_update', request_method='POST',
466 renderer='rhodecode:templates/admin/permissions/permissions.mako')
470 renderer='rhodecode:templates/admin/permissions/permissions.mako')
467 def ssh_keys_update(self):
471 def ssh_keys_update(self):
468 _ = self.request.translate
472 _ = self.request.translate
469 self.load_default_context()
473 self.load_default_context()
470
474
471 ssh_enabled = self.ssh_enabled()
475 ssh_enabled = self.ssh_enabled()
472 key_file = self.request.registry.settings.get(
476 key_file = self.request.registry.settings.get(
473 'ssh.authorized_keys_file_path')
477 'ssh.authorized_keys_file_path')
474 if ssh_enabled:
478 if ssh_enabled:
475 trigger(SshKeyFileChangeEvent(), self.request.registry)
479 trigger(SshKeyFileChangeEvent(), self.request.registry)
476 h.flash(_('Updated SSH keys file: {}').format(key_file),
480 h.flash(_('Updated SSH keys file: {}').format(key_file),
477 category='success')
481 category='success')
478 else:
482 else:
479 h.flash(_('SSH key support is disabled in .ini file'),
483 h.flash(_('SSH key support is disabled in .ini file'),
480 category='warning')
484 category='warning')
481
485
482 raise HTTPFound(h.route_path('admin_permissions_ssh_keys'))
486 raise HTTPFound(h.route_path('admin_permissions_ssh_keys'))
@@ -1,91 +1,91 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22
22
23 import psutil
23 import psutil
24 from pyramid.view import view_config
24 from pyramid.view import view_config
25
25
26 from rhodecode.apps._base import BaseAppView
26 from rhodecode.apps._base import BaseAppView
27 from rhodecode.apps.admin.navigation import navigation_list
27 from rhodecode.apps.admin.navigation import navigation_list
28 from rhodecode.lib.auth import (
28 from rhodecode.lib.auth import (
29 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
29 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
30 from rhodecode.lib.utils2 import safe_int
30 from rhodecode.lib.utils2 import safe_int
31
31
32 log = logging.getLogger(__name__)
32 log = logging.getLogger(__name__)
33
33
34
34
35 class AdminProcessManagementView(BaseAppView):
35 class AdminProcessManagementView(BaseAppView):
36 def load_default_context(self):
36 def load_default_context(self):
37 c = self._get_local_tmpl_context()
37 c = self._get_local_tmpl_context()
38 self._register_global_c(c)
38
39 return c
39 return c
40
40
41 @LoginRequired()
41 @LoginRequired()
42 @HasPermissionAllDecorator('hg.admin')
42 @HasPermissionAllDecorator('hg.admin')
43 @view_config(
43 @view_config(
44 route_name='admin_settings_process_management', request_method='GET',
44 route_name='admin_settings_process_management', request_method='GET',
45 renderer='rhodecode:templates/admin/settings/settings.mako')
45 renderer='rhodecode:templates/admin/settings/settings.mako')
46 def process_management(self):
46 def process_management(self):
47 _ = self.request.translate
47 _ = self.request.translate
48 c = self.load_default_context()
48 c = self.load_default_context()
49
49
50 c.active = 'process_management'
50 c.active = 'process_management'
51 c.navlist = navigation_list(self.request)
51 c.navlist = navigation_list(self.request)
52 c.gunicorn_processes = (
52 c.gunicorn_processes = (
53 p for p in psutil.process_iter() if 'gunicorn' in p.name())
53 p for p in psutil.process_iter() if 'gunicorn' in p.name())
54 return self._get_template_context(c)
54 return self._get_template_context(c)
55
55
56 @LoginRequired()
56 @LoginRequired()
57 @HasPermissionAllDecorator('hg.admin')
57 @HasPermissionAllDecorator('hg.admin')
58 @CSRFRequired()
58 @CSRFRequired()
59 @view_config(
59 @view_config(
60 route_name='admin_settings_process_management_signal',
60 route_name='admin_settings_process_management_signal',
61 request_method='POST', renderer='json_ext')
61 request_method='POST', renderer='json_ext')
62 def process_management_signal(self):
62 def process_management_signal(self):
63 pids = self.request.json.get('pids', [])
63 pids = self.request.json.get('pids', [])
64 result = []
64 result = []
65 def on_terminate(proc):
65 def on_terminate(proc):
66 msg = "process `PID:{}` terminated with exit code {}".format(
66 msg = "process `PID:{}` terminated with exit code {}".format(
67 proc.pid, proc.returncode)
67 proc.pid, proc.returncode)
68 result.append(msg)
68 result.append(msg)
69
69
70 procs = []
70 procs = []
71 for pid in pids:
71 for pid in pids:
72 pid = safe_int(pid)
72 pid = safe_int(pid)
73 if pid:
73 if pid:
74 try:
74 try:
75 proc = psutil.Process(pid)
75 proc = psutil.Process(pid)
76 except psutil.NoSuchProcess:
76 except psutil.NoSuchProcess:
77 continue
77 continue
78
78
79 children = proc.children(recursive=True)
79 children = proc.children(recursive=True)
80 if children:
80 if children:
81 print('Wont kill Master Process')
81 print('Wont kill Master Process')
82 else:
82 else:
83 procs.append(proc)
83 procs.append(proc)
84
84
85 for p in procs:
85 for p in procs:
86 p.terminate()
86 p.terminate()
87 gone, alive = psutil.wait_procs(procs, timeout=10, callback=on_terminate)
87 gone, alive = psutil.wait_procs(procs, timeout=10, callback=on_terminate)
88 for p in alive:
88 for p in alive:
89 p.kill()
89 p.kill()
90
90
91 return {'result': result}
91 return {'result': result}
@@ -1,204 +1,205 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 import formencode
22 import formencode
23 import formencode.htmlfill
23 import formencode.htmlfill
24
24
25 from pyramid.httpexceptions import HTTPFound, HTTPForbidden
25 from pyramid.httpexceptions import HTTPFound, HTTPForbidden
26 from pyramid.view import view_config
26 from pyramid.view import view_config
27 from pyramid.renderers import render
27 from pyramid.renderers import render
28 from pyramid.response import Response
28 from pyramid.response import Response
29
29
30 from rhodecode.apps._base import BaseAppView, DataGridAppView
30 from rhodecode.apps._base import BaseAppView, DataGridAppView
31
31
32 from rhodecode.lib.ext_json import json
32 from rhodecode.lib.ext_json import json
33 from rhodecode.lib.auth import (
33 from rhodecode.lib.auth import (
34 LoginRequired, CSRFRequired, NotAnonymous,
34 LoginRequired, CSRFRequired, NotAnonymous,
35 HasPermissionAny, HasRepoGroupPermissionAny)
35 HasPermissionAny, HasRepoGroupPermissionAny)
36 from rhodecode.lib import helpers as h, audit_logger
36 from rhodecode.lib import helpers as h, audit_logger
37 from rhodecode.lib.utils2 import safe_int, safe_unicode
37 from rhodecode.lib.utils2 import safe_int, safe_unicode
38 from rhodecode.model.forms import RepoGroupForm
38 from rhodecode.model.forms import RepoGroupForm
39 from rhodecode.model.repo_group import RepoGroupModel
39 from rhodecode.model.repo_group import RepoGroupModel
40 from rhodecode.model.scm import RepoGroupList
40 from rhodecode.model.scm import RepoGroupList
41 from rhodecode.model.db import Session, RepoGroup
41 from rhodecode.model.db import Session, RepoGroup
42
42
43 log = logging.getLogger(__name__)
43 log = logging.getLogger(__name__)
44
44
45
45
46 class AdminRepoGroupsView(BaseAppView, DataGridAppView):
46 class AdminRepoGroupsView(BaseAppView, DataGridAppView):
47
47
48 def load_default_context(self):
48 def load_default_context(self):
49 c = self._get_local_tmpl_context()
49 c = self._get_local_tmpl_context()
50 self._register_global_c(c)
50
51 return c
51 return c
52
52
53 def _load_form_data(self, c):
53 def _load_form_data(self, c):
54 allow_empty_group = False
54 allow_empty_group = False
55
55
56 if self._can_create_repo_group():
56 if self._can_create_repo_group():
57 # we're global admin, we're ok and we can create TOP level groups
57 # we're global admin, we're ok and we can create TOP level groups
58 allow_empty_group = True
58 allow_empty_group = True
59
59
60 # override the choices for this form, we need to filter choices
60 # override the choices for this form, we need to filter choices
61 # and display only those we have ADMIN right
61 # and display only those we have ADMIN right
62 groups_with_admin_rights = RepoGroupList(
62 groups_with_admin_rights = RepoGroupList(
63 RepoGroup.query().all(),
63 RepoGroup.query().all(),
64 perm_set=['group.admin'])
64 perm_set=['group.admin'])
65 c.repo_groups = RepoGroup.groups_choices(
65 c.repo_groups = RepoGroup.groups_choices(
66 groups=groups_with_admin_rights,
66 groups=groups_with_admin_rights,
67 show_empty_group=allow_empty_group)
67 show_empty_group=allow_empty_group)
68
68
69 def _can_create_repo_group(self, parent_group_id=None):
69 def _can_create_repo_group(self, parent_group_id=None):
70 is_admin = HasPermissionAny('hg.admin')('group create controller')
70 is_admin = HasPermissionAny('hg.admin')('group create controller')
71 create_repo_group = HasPermissionAny(
71 create_repo_group = HasPermissionAny(
72 'hg.repogroup.create.true')('group create controller')
72 'hg.repogroup.create.true')('group create controller')
73 if is_admin or (create_repo_group and not parent_group_id):
73 if is_admin or (create_repo_group and not parent_group_id):
74 # we're global admin, or we have global repo group create
74 # we're global admin, or we have global repo group create
75 # permission
75 # permission
76 # we're ok and we can create TOP level groups
76 # we're ok and we can create TOP level groups
77 return True
77 return True
78 elif parent_group_id:
78 elif parent_group_id:
79 # we check the permission if we can write to parent group
79 # we check the permission if we can write to parent group
80 group = RepoGroup.get(parent_group_id)
80 group = RepoGroup.get(parent_group_id)
81 group_name = group.group_name if group else None
81 group_name = group.group_name if group else None
82 if HasRepoGroupPermissionAny('group.admin')(
82 if HasRepoGroupPermissionAny('group.admin')(
83 group_name, 'check if user is an admin of group'):
83 group_name, 'check if user is an admin of group'):
84 # we're an admin of passed in group, we're ok.
84 # we're an admin of passed in group, we're ok.
85 return True
85 return True
86 else:
86 else:
87 return False
87 return False
88 return False
88 return False
89
89
90 @LoginRequired()
90 @LoginRequired()
91 @NotAnonymous()
91 @NotAnonymous()
92 # perms check inside
92 # perms check inside
93 @view_config(
93 @view_config(
94 route_name='repo_groups', request_method='GET',
94 route_name='repo_groups', request_method='GET',
95 renderer='rhodecode:templates/admin/repo_groups/repo_groups.mako')
95 renderer='rhodecode:templates/admin/repo_groups/repo_groups.mako')
96 def repo_group_list(self):
96 def repo_group_list(self):
97 c = self.load_default_context()
97 c = self.load_default_context()
98
98
99 repo_group_list = RepoGroup.get_all_repo_groups()
99 repo_group_list = RepoGroup.get_all_repo_groups()
100 repo_group_list_acl = RepoGroupList(
100 repo_group_list_acl = RepoGroupList(
101 repo_group_list, perm_set=['group.admin'])
101 repo_group_list, perm_set=['group.admin'])
102 repo_group_data = RepoGroupModel().get_repo_groups_as_dict(
102 repo_group_data = RepoGroupModel().get_repo_groups_as_dict(
103 repo_group_list=repo_group_list_acl, admin=True)
103 repo_group_list=repo_group_list_acl, admin=True)
104 c.data = json.dumps(repo_group_data)
104 c.data = json.dumps(repo_group_data)
105 return self._get_template_context(c)
105 return self._get_template_context(c)
106
106
107 @LoginRequired()
107 @LoginRequired()
108 @NotAnonymous()
108 @NotAnonymous()
109 # perm checks inside
109 # perm checks inside
110 @view_config(
110 @view_config(
111 route_name='repo_group_new', request_method='GET',
111 route_name='repo_group_new', request_method='GET',
112 renderer='rhodecode:templates/admin/repo_groups/repo_group_add.mako')
112 renderer='rhodecode:templates/admin/repo_groups/repo_group_add.mako')
113 def repo_group_new(self):
113 def repo_group_new(self):
114 c = self.load_default_context()
114 c = self.load_default_context()
115
115
116 # perm check for admin, create_group perm or admin of parent_group
116 # perm check for admin, create_group perm or admin of parent_group
117 parent_group_id = safe_int(self.request.GET.get('parent_group'))
117 parent_group_id = safe_int(self.request.GET.get('parent_group'))
118 if not self._can_create_repo_group(parent_group_id):
118 if not self._can_create_repo_group(parent_group_id):
119 raise HTTPForbidden()
119 raise HTTPForbidden()
120
120
121 self._load_form_data(c)
121 self._load_form_data(c)
122
122
123 defaults = {} # Future proof for default of repo group
123 defaults = {} # Future proof for default of repo group
124 data = render(
124 data = render(
125 'rhodecode:templates/admin/repo_groups/repo_group_add.mako',
125 'rhodecode:templates/admin/repo_groups/repo_group_add.mako',
126 self._get_template_context(c), self.request)
126 self._get_template_context(c), self.request)
127 html = formencode.htmlfill.render(
127 html = formencode.htmlfill.render(
128 data,
128 data,
129 defaults=defaults,
129 defaults=defaults,
130 encoding="UTF-8",
130 encoding="UTF-8",
131 force_defaults=False
131 force_defaults=False
132 )
132 )
133 return Response(html)
133 return Response(html)
134
134
135 @LoginRequired()
135 @LoginRequired()
136 @NotAnonymous()
136 @NotAnonymous()
137 @CSRFRequired()
137 @CSRFRequired()
138 # perm checks inside
138 # perm checks inside
139 @view_config(
139 @view_config(
140 route_name='repo_group_create', request_method='POST',
140 route_name='repo_group_create', request_method='POST',
141 renderer='rhodecode:templates/admin/repo_groups/repo_group_add.mako')
141 renderer='rhodecode:templates/admin/repo_groups/repo_group_add.mako')
142 def repo_group_create(self):
142 def repo_group_create(self):
143 c = self.load_default_context()
143 c = self.load_default_context()
144 _ = self.request.translate
144 _ = self.request.translate
145
145
146 parent_group_id = safe_int(self.request.POST.get('group_parent_id'))
146 parent_group_id = safe_int(self.request.POST.get('group_parent_id'))
147 can_create = self._can_create_repo_group(parent_group_id)
147 can_create = self._can_create_repo_group(parent_group_id)
148
148
149 self._load_form_data(c)
149 self._load_form_data(c)
150 # permissions for can create group based on parent_id are checked
150 # permissions for can create group based on parent_id are checked
151 # here in the Form
151 # here in the Form
152 available_groups = map(lambda k: safe_unicode(k[0]), c.repo_groups)
152 available_groups = map(lambda k: safe_unicode(k[0]), c.repo_groups)
153 repo_group_form = RepoGroupForm(available_groups=available_groups,
153 repo_group_form = RepoGroupForm(
154 can_create_in_root=can_create)()
154 self.request.translate, available_groups=available_groups,
155 can_create_in_root=can_create)()
155
156
156 repo_group_name = self.request.POST.get('group_name')
157 repo_group_name = self.request.POST.get('group_name')
157 try:
158 try:
158 owner = self._rhodecode_user
159 owner = self._rhodecode_user
159 form_result = repo_group_form.to_python(dict(self.request.POST))
160 form_result = repo_group_form.to_python(dict(self.request.POST))
160 repo_group = RepoGroupModel().create(
161 repo_group = RepoGroupModel().create(
161 group_name=form_result['group_name_full'],
162 group_name=form_result['group_name_full'],
162 group_description=form_result['group_description'],
163 group_description=form_result['group_description'],
163 owner=owner.user_id,
164 owner=owner.user_id,
164 copy_permissions=form_result['group_copy_permissions']
165 copy_permissions=form_result['group_copy_permissions']
165 )
166 )
166 Session().flush()
167 Session().flush()
167
168
168 repo_group_data = repo_group.get_api_data()
169 repo_group_data = repo_group.get_api_data()
169 audit_logger.store_web(
170 audit_logger.store_web(
170 'repo_group.create', action_data={'data': repo_group_data},
171 'repo_group.create', action_data={'data': repo_group_data},
171 user=self._rhodecode_user)
172 user=self._rhodecode_user)
172
173
173 Session().commit()
174 Session().commit()
174
175
175 _new_group_name = form_result['group_name_full']
176 _new_group_name = form_result['group_name_full']
176
177
177 repo_group_url = h.link_to(
178 repo_group_url = h.link_to(
178 _new_group_name,
179 _new_group_name,
179 h.route_path('repo_group_home', repo_group_name=_new_group_name))
180 h.route_path('repo_group_home', repo_group_name=_new_group_name))
180 h.flash(h.literal(_('Created repository group %s')
181 h.flash(h.literal(_('Created repository group %s')
181 % repo_group_url), category='success')
182 % repo_group_url), category='success')
182
183
183 except formencode.Invalid as errors:
184 except formencode.Invalid as errors:
184 data = render(
185 data = render(
185 'rhodecode:templates/admin/repo_groups/repo_group_add.mako',
186 'rhodecode:templates/admin/repo_groups/repo_group_add.mako',
186 self._get_template_context(c), self.request)
187 self._get_template_context(c), self.request)
187 html = formencode.htmlfill.render(
188 html = formencode.htmlfill.render(
188 data,
189 data,
189 defaults=errors.value,
190 defaults=errors.value,
190 errors=errors.error_dict or {},
191 errors=errors.error_dict or {},
191 prefix_error=False,
192 prefix_error=False,
192 encoding="UTF-8",
193 encoding="UTF-8",
193 force_defaults=False
194 force_defaults=False
194 )
195 )
195 return Response(html)
196 return Response(html)
196 except Exception:
197 except Exception:
197 log.exception("Exception during creation of repository group")
198 log.exception("Exception during creation of repository group")
198 h.flash(_('Error occurred during creation of repository group %s')
199 h.flash(_('Error occurred during creation of repository group %s')
199 % repo_group_name, category='error')
200 % repo_group_name, category='error')
200 raise HTTPFound(h.route_path('home'))
201 raise HTTPFound(h.route_path('home'))
201
202
202 raise HTTPFound(
203 raise HTTPFound(
203 h.route_path('repo_group_home',
204 h.route_path('repo_group_home',
204 repo_group_name=form_result['group_name_full']))
205 repo_group_name=form_result['group_name_full']))
@@ -1,182 +1,183 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 import formencode
22 import formencode
23 import formencode.htmlfill
23 import formencode.htmlfill
24
24
25 from pyramid.httpexceptions import HTTPFound, HTTPForbidden
25 from pyramid.httpexceptions import HTTPFound, HTTPForbidden
26 from pyramid.view import view_config
26 from pyramid.view import view_config
27 from pyramid.renderers import render
27 from pyramid.renderers import render
28 from pyramid.response import Response
28 from pyramid.response import Response
29
29
30 from rhodecode.apps._base import BaseAppView, DataGridAppView
30 from rhodecode.apps._base import BaseAppView, DataGridAppView
31
31
32 from rhodecode.lib.ext_json import json
32 from rhodecode.lib.ext_json import json
33 from rhodecode.lib.auth import (
33 from rhodecode.lib.auth import (
34 LoginRequired, CSRFRequired, NotAnonymous,
34 LoginRequired, CSRFRequired, NotAnonymous,
35 HasPermissionAny, HasRepoGroupPermissionAny)
35 HasPermissionAny, HasRepoGroupPermissionAny)
36 from rhodecode.lib import helpers as h
36 from rhodecode.lib import helpers as h
37 from rhodecode.lib.utils import repo_name_slug
37 from rhodecode.lib.utils import repo_name_slug
38 from rhodecode.lib.utils2 import safe_int, safe_unicode
38 from rhodecode.lib.utils2 import safe_int, safe_unicode
39 from rhodecode.model.forms import RepoForm
39 from rhodecode.model.forms import RepoForm
40 from rhodecode.model.repo import RepoModel
40 from rhodecode.model.repo import RepoModel
41 from rhodecode.model.scm import RepoList, RepoGroupList, ScmModel
41 from rhodecode.model.scm import RepoList, RepoGroupList, ScmModel
42 from rhodecode.model.settings import SettingsModel
42 from rhodecode.model.settings import SettingsModel
43 from rhodecode.model.db import Repository, RepoGroup
43 from rhodecode.model.db import Repository, RepoGroup
44
44
45 log = logging.getLogger(__name__)
45 log = logging.getLogger(__name__)
46
46
47
47
48 class AdminReposView(BaseAppView, DataGridAppView):
48 class AdminReposView(BaseAppView, DataGridAppView):
49
49
50 def load_default_context(self):
50 def load_default_context(self):
51 c = self._get_local_tmpl_context()
51 c = self._get_local_tmpl_context()
52 self._register_global_c(c)
52
53 return c
53 return c
54
54
55 def _load_form_data(self, c):
55 def _load_form_data(self, c):
56 acl_groups = RepoGroupList(RepoGroup.query().all(),
56 acl_groups = RepoGroupList(RepoGroup.query().all(),
57 perm_set=['group.write', 'group.admin'])
57 perm_set=['group.write', 'group.admin'])
58 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
58 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
59 c.repo_groups_choices = map(lambda k: safe_unicode(k[0]), c.repo_groups)
59 c.repo_groups_choices = map(lambda k: safe_unicode(k[0]), c.repo_groups)
60 c.landing_revs_choices, c.landing_revs = \
60 c.landing_revs_choices, c.landing_revs = \
61 ScmModel().get_repo_landing_revs()
61 ScmModel().get_repo_landing_revs()
62 c.personal_repo_group = self._rhodecode_user.personal_repo_group
62 c.personal_repo_group = self._rhodecode_user.personal_repo_group
63
63
64 @LoginRequired()
64 @LoginRequired()
65 @NotAnonymous()
65 @NotAnonymous()
66 # perms check inside
66 # perms check inside
67 @view_config(
67 @view_config(
68 route_name='repos', request_method='GET',
68 route_name='repos', request_method='GET',
69 renderer='rhodecode:templates/admin/repos/repos.mako')
69 renderer='rhodecode:templates/admin/repos/repos.mako')
70 def repository_list(self):
70 def repository_list(self):
71 c = self.load_default_context()
71 c = self.load_default_context()
72
72
73 repo_list = Repository.get_all_repos()
73 repo_list = Repository.get_all_repos()
74 c.repo_list = RepoList(repo_list, perm_set=['repository.admin'])
74 c.repo_list = RepoList(repo_list, perm_set=['repository.admin'])
75 repos_data = RepoModel().get_repos_as_dict(
75 repos_data = RepoModel().get_repos_as_dict(
76 repo_list=c.repo_list, admin=True, super_user_actions=True)
76 repo_list=c.repo_list, admin=True, super_user_actions=True)
77 # json used to render the grid
77 # json used to render the grid
78 c.data = json.dumps(repos_data)
78 c.data = json.dumps(repos_data)
79
79
80 return self._get_template_context(c)
80 return self._get_template_context(c)
81
81
82 @LoginRequired()
82 @LoginRequired()
83 @NotAnonymous()
83 @NotAnonymous()
84 # perms check inside
84 # perms check inside
85 @view_config(
85 @view_config(
86 route_name='repo_new', request_method='GET',
86 route_name='repo_new', request_method='GET',
87 renderer='rhodecode:templates/admin/repos/repo_add.mako')
87 renderer='rhodecode:templates/admin/repos/repo_add.mako')
88 def repository_new(self):
88 def repository_new(self):
89 c = self.load_default_context()
89 c = self.load_default_context()
90
90
91 new_repo = self.request.GET.get('repo', '')
91 new_repo = self.request.GET.get('repo', '')
92 parent_group = safe_int(self.request.GET.get('parent_group'))
92 parent_group = safe_int(self.request.GET.get('parent_group'))
93 _gr = RepoGroup.get(parent_group)
93 _gr = RepoGroup.get(parent_group)
94
94
95 if not HasPermissionAny('hg.admin', 'hg.create.repository')():
95 if not HasPermissionAny('hg.admin', 'hg.create.repository')():
96 # you're not super admin nor have global create permissions,
96 # you're not super admin nor have global create permissions,
97 # but maybe you have at least write permission to a parent group ?
97 # but maybe you have at least write permission to a parent group ?
98
98
99 gr_name = _gr.group_name if _gr else None
99 gr_name = _gr.group_name if _gr else None
100 # create repositories with write permission on group is set to true
100 # create repositories with write permission on group is set to true
101 create_on_write = HasPermissionAny('hg.create.write_on_repogroup.true')()
101 create_on_write = HasPermissionAny('hg.create.write_on_repogroup.true')()
102 group_admin = HasRepoGroupPermissionAny('group.admin')(group_name=gr_name)
102 group_admin = HasRepoGroupPermissionAny('group.admin')(group_name=gr_name)
103 group_write = HasRepoGroupPermissionAny('group.write')(group_name=gr_name)
103 group_write = HasRepoGroupPermissionAny('group.write')(group_name=gr_name)
104 if not (group_admin or (group_write and create_on_write)):
104 if not (group_admin or (group_write and create_on_write)):
105 raise HTTPForbidden()
105 raise HTTPForbidden()
106
106
107 self._load_form_data(c)
107 self._load_form_data(c)
108 c.new_repo = repo_name_slug(new_repo)
108 c.new_repo = repo_name_slug(new_repo)
109
109
110 # apply the defaults from defaults page
110 # apply the defaults from defaults page
111 defaults = SettingsModel().get_default_repo_settings(strip_prefix=True)
111 defaults = SettingsModel().get_default_repo_settings(strip_prefix=True)
112 # set checkbox to autochecked
112 # set checkbox to autochecked
113 defaults['repo_copy_permissions'] = True
113 defaults['repo_copy_permissions'] = True
114
114
115 parent_group_choice = '-1'
115 parent_group_choice = '-1'
116 if not self._rhodecode_user.is_admin and self._rhodecode_user.personal_repo_group:
116 if not self._rhodecode_user.is_admin and self._rhodecode_user.personal_repo_group:
117 parent_group_choice = self._rhodecode_user.personal_repo_group
117 parent_group_choice = self._rhodecode_user.personal_repo_group
118
118
119 if parent_group and _gr:
119 if parent_group and _gr:
120 if parent_group in [x[0] for x in c.repo_groups]:
120 if parent_group in [x[0] for x in c.repo_groups]:
121 parent_group_choice = safe_unicode(parent_group)
121 parent_group_choice = safe_unicode(parent_group)
122
122
123 defaults.update({'repo_group': parent_group_choice})
123 defaults.update({'repo_group': parent_group_choice})
124
124
125 data = render('rhodecode:templates/admin/repos/repo_add.mako',
125 data = render('rhodecode:templates/admin/repos/repo_add.mako',
126 self._get_template_context(c), self.request)
126 self._get_template_context(c), self.request)
127 html = formencode.htmlfill.render(
127 html = formencode.htmlfill.render(
128 data,
128 data,
129 defaults=defaults,
129 defaults=defaults,
130 encoding="UTF-8",
130 encoding="UTF-8",
131 force_defaults=False
131 force_defaults=False
132 )
132 )
133 return Response(html)
133 return Response(html)
134
134
135 @LoginRequired()
135 @LoginRequired()
136 @NotAnonymous()
136 @NotAnonymous()
137 @CSRFRequired()
137 @CSRFRequired()
138 # perms check inside
138 # perms check inside
139 @view_config(
139 @view_config(
140 route_name='repo_create', request_method='POST',
140 route_name='repo_create', request_method='POST',
141 renderer='rhodecode:templates/admin/repos/repos.mako')
141 renderer='rhodecode:templates/admin/repos/repos.mako')
142 def repository_create(self):
142 def repository_create(self):
143 c = self.load_default_context()
143 c = self.load_default_context()
144
144
145 form_result = {}
145 form_result = {}
146 task_id = None
146 task_id = None
147 self._load_form_data(c)
147 self._load_form_data(c)
148
148
149 try:
149 try:
150 # CanWriteToGroup validators checks permissions of this POST
150 # CanWriteToGroup validators checks permissions of this POST
151 form_result = RepoForm(repo_groups=c.repo_groups_choices,
151 form = RepoForm(
152 landing_revs=c.landing_revs_choices)()\
152 self.request.translate, repo_groups=c.repo_groups_choices,
153 .to_python(dict(self.request.POST))
153 landing_revs=c.landing_revs_choices)()
154 form_results = form.to_python(dict(self.request.POST))
154
155
155 # create is done sometimes async on celery, db transaction
156 # create is done sometimes async on celery, db transaction
156 # management is handled there.
157 # management is handled there.
157 task = RepoModel().create(form_result, self._rhodecode_user.user_id)
158 task = RepoModel().create(form_result, self._rhodecode_user.user_id)
158 from celery.result import BaseAsyncResult
159 from celery.result import BaseAsyncResult
159 if isinstance(task, BaseAsyncResult):
160 if isinstance(task, BaseAsyncResult):
160 task_id = task.task_id
161 task_id = task.task_id
161 except formencode.Invalid as errors:
162 except formencode.Invalid as errors:
162 data = render('rhodecode:templates/admin/repos/repo_add.mako',
163 data = render('rhodecode:templates/admin/repos/repo_add.mako',
163 self._get_template_context(c), self.request)
164 self._get_template_context(c), self.request)
164 html = formencode.htmlfill.render(
165 html = formencode.htmlfill.render(
165 data,
166 data,
166 defaults=errors.value,
167 defaults=errors.value,
167 errors=errors.error_dict or {},
168 errors=errors.error_dict or {},
168 prefix_error=False,
169 prefix_error=False,
169 encoding="UTF-8",
170 encoding="UTF-8",
170 force_defaults=False
171 force_defaults=False
171 )
172 )
172 return Response(html)
173 return Response(html)
173
174
174 except Exception as e:
175 except Exception as e:
175 msg = self._log_creation_exception(e, form_result.get('repo_name'))
176 msg = self._log_creation_exception(e, form_result.get('repo_name'))
176 h.flash(msg, category='error')
177 h.flash(msg, category='error')
177 raise HTTPFound(h.route_path('home'))
178 raise HTTPFound(h.route_path('home'))
178
179
179 raise HTTPFound(
180 raise HTTPFound(
180 h.route_path('repo_creating',
181 h.route_path('repo_creating',
181 repo_name=form_result['repo_name_full'],
182 repo_name=form_result['repo_name_full'],
182 _query=dict(task_id=task_id)))
183 _query=dict(task_id=task_id)))
@@ -1,102 +1,102 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22
22
23 from pyramid.view import view_config
23 from pyramid.view import view_config
24 from pyramid.httpexceptions import HTTPFound
24 from pyramid.httpexceptions import HTTPFound
25
25
26 from rhodecode.apps._base import BaseAppView
26 from rhodecode.apps._base import BaseAppView
27 from rhodecode.apps.admin.navigation import navigation_list
27 from rhodecode.apps.admin.navigation import navigation_list
28 from rhodecode.lib.auth import (
28 from rhodecode.lib.auth import (
29 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
29 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
30 from rhodecode.lib.utils2 import safe_int
30 from rhodecode.lib.utils2 import safe_int
31 from rhodecode.lib import system_info
31 from rhodecode.lib import system_info
32 from rhodecode.lib import user_sessions
32 from rhodecode.lib import user_sessions
33
33
34
34
35 log = logging.getLogger(__name__)
35 log = logging.getLogger(__name__)
36
36
37
37
38 class AdminSessionSettingsView(BaseAppView):
38 class AdminSessionSettingsView(BaseAppView):
39 def load_default_context(self):
39 def load_default_context(self):
40 c = self._get_local_tmpl_context()
40 c = self._get_local_tmpl_context()
41
41
42 self._register_global_c(c)
42
43 return c
43 return c
44
44
45 @LoginRequired()
45 @LoginRequired()
46 @HasPermissionAllDecorator('hg.admin')
46 @HasPermissionAllDecorator('hg.admin')
47 @view_config(
47 @view_config(
48 route_name='admin_settings_sessions', request_method='GET',
48 route_name='admin_settings_sessions', request_method='GET',
49 renderer='rhodecode:templates/admin/settings/settings.mako')
49 renderer='rhodecode:templates/admin/settings/settings.mako')
50 def settings_sessions(self):
50 def settings_sessions(self):
51 c = self.load_default_context()
51 c = self.load_default_context()
52
52
53 c.active = 'sessions'
53 c.active = 'sessions'
54 c.navlist = navigation_list(self.request)
54 c.navlist = navigation_list(self.request)
55
55
56 c.cleanup_older_days = 60
56 c.cleanup_older_days = 60
57 older_than_seconds = 60 * 60 * 24 * c.cleanup_older_days
57 older_than_seconds = 60 * 60 * 24 * c.cleanup_older_days
58
58
59 config = system_info.rhodecode_config().get_value()['value']['config']
59 config = system_info.rhodecode_config().get_value()['value']['config']
60 c.session_model = user_sessions.get_session_handler(
60 c.session_model = user_sessions.get_session_handler(
61 config.get('beaker.session.type', 'memory'))(config)
61 config.get('beaker.session.type', 'memory'))(config)
62
62
63 c.session_conf = c.session_model.config
63 c.session_conf = c.session_model.config
64 c.session_count = c.session_model.get_count()
64 c.session_count = c.session_model.get_count()
65 c.session_expired_count = c.session_model.get_expired_count(
65 c.session_expired_count = c.session_model.get_expired_count(
66 older_than_seconds)
66 older_than_seconds)
67
67
68 return self._get_template_context(c)
68 return self._get_template_context(c)
69
69
70 @LoginRequired()
70 @LoginRequired()
71 @HasPermissionAllDecorator('hg.admin')
71 @HasPermissionAllDecorator('hg.admin')
72 @CSRFRequired()
72 @CSRFRequired()
73 @view_config(
73 @view_config(
74 route_name='admin_settings_sessions_cleanup', request_method='POST')
74 route_name='admin_settings_sessions_cleanup', request_method='POST')
75 def settings_sessions_cleanup(self):
75 def settings_sessions_cleanup(self):
76 _ = self.request.translate
76 _ = self.request.translate
77 expire_days = safe_int(self.request.params.get('expire_days'))
77 expire_days = safe_int(self.request.params.get('expire_days'))
78
78
79 if expire_days is None:
79 if expire_days is None:
80 expire_days = 60
80 expire_days = 60
81
81
82 older_than_seconds = 60 * 60 * 24 * expire_days
82 older_than_seconds = 60 * 60 * 24 * expire_days
83
83
84 config = system_info.rhodecode_config().get_value()['value']['config']
84 config = system_info.rhodecode_config().get_value()['value']['config']
85 session_model = user_sessions.get_session_handler(
85 session_model = user_sessions.get_session_handler(
86 config.get('beaker.session.type', 'memory'))(config)
86 config.get('beaker.session.type', 'memory'))(config)
87
87
88 try:
88 try:
89 session_model.clean_sessions(
89 session_model.clean_sessions(
90 older_than_seconds=older_than_seconds)
90 older_than_seconds=older_than_seconds)
91 self.request.session.flash(
91 self.request.session.flash(
92 _('Cleaned up old sessions'), queue='success')
92 _('Cleaned up old sessions'), queue='success')
93 except user_sessions.CleanupCommand as msg:
93 except user_sessions.CleanupCommand as msg:
94 self.request.session.flash(msg.message, queue='warning')
94 self.request.session.flash(msg.message, queue='warning')
95 except Exception as e:
95 except Exception as e:
96 log.exception('Failed session cleanup')
96 log.exception('Failed session cleanup')
97 self.request.session.flash(
97 self.request.session.flash(
98 _('Failed to cleanup up old sessions'), queue='error')
98 _('Failed to cleanup up old sessions'), queue='error')
99
99
100 redirect_to = self.request.resource_path(
100 redirect_to = self.request.resource_path(
101 self.context, route_name='admin_settings_sessions')
101 self.context, route_name='admin_settings_sessions')
102 return HTTPFound(redirect_to)
102 return HTTPFound(redirect_to)
@@ -1,762 +1,762 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import logging
22 import logging
23 import collections
23 import collections
24
24
25 import datetime
25 import datetime
26 import formencode
26 import formencode
27 import formencode.htmlfill
27 import formencode.htmlfill
28
28
29 import rhodecode
29 import rhodecode
30 from pyramid.view import view_config
30 from pyramid.view import view_config
31 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
31 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
32 from pyramid.renderers import render
32 from pyramid.renderers import render
33 from pyramid.response import Response
33 from pyramid.response import Response
34
34
35 from rhodecode.apps._base import BaseAppView
35 from rhodecode.apps._base import BaseAppView
36 from rhodecode.apps.admin.navigation import navigation_list
36 from rhodecode.apps.admin.navigation import navigation_list
37 from rhodecode.apps.svn_support.config_keys import generate_config
37 from rhodecode.apps.svn_support.config_keys import generate_config
38 from rhodecode.lib import helpers as h
38 from rhodecode.lib import helpers as h
39 from rhodecode.lib.auth import (
39 from rhodecode.lib.auth import (
40 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
40 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
41 from rhodecode.lib.celerylib import tasks, run_task
41 from rhodecode.lib.celerylib import tasks, run_task
42 from rhodecode.lib.utils import repo2db_mapper
42 from rhodecode.lib.utils import repo2db_mapper
43 from rhodecode.lib.utils2 import str2bool, safe_unicode, AttributeDict
43 from rhodecode.lib.utils2 import str2bool, safe_unicode, AttributeDict
44 from rhodecode.lib.index import searcher_from_config
44 from rhodecode.lib.index import searcher_from_config
45
45
46 from rhodecode.model.db import RhodeCodeUi, Repository
46 from rhodecode.model.db import RhodeCodeUi, Repository
47 from rhodecode.model.forms import (ApplicationSettingsForm,
47 from rhodecode.model.forms import (ApplicationSettingsForm,
48 ApplicationUiSettingsForm, ApplicationVisualisationForm,
48 ApplicationUiSettingsForm, ApplicationVisualisationForm,
49 LabsSettingsForm, IssueTrackerPatternsForm)
49 LabsSettingsForm, IssueTrackerPatternsForm)
50 from rhodecode.model.repo_group import RepoGroupModel
50 from rhodecode.model.repo_group import RepoGroupModel
51
51
52 from rhodecode.model.scm import ScmModel
52 from rhodecode.model.scm import ScmModel
53 from rhodecode.model.notification import EmailNotificationModel
53 from rhodecode.model.notification import EmailNotificationModel
54 from rhodecode.model.meta import Session
54 from rhodecode.model.meta import Session
55 from rhodecode.model.settings import (
55 from rhodecode.model.settings import (
56 IssueTrackerSettingsModel, VcsSettingsModel, SettingNotFound,
56 IssueTrackerSettingsModel, VcsSettingsModel, SettingNotFound,
57 SettingsModel)
57 SettingsModel)
58
58
59
59
60 log = logging.getLogger(__name__)
60 log = logging.getLogger(__name__)
61
61
62
62
63 class AdminSettingsView(BaseAppView):
63 class AdminSettingsView(BaseAppView):
64
64
65 def load_default_context(self):
65 def load_default_context(self):
66 c = self._get_local_tmpl_context()
66 c = self._get_local_tmpl_context()
67 c.labs_active = str2bool(
67 c.labs_active = str2bool(
68 rhodecode.CONFIG.get('labs_settings_active', 'true'))
68 rhodecode.CONFIG.get('labs_settings_active', 'true'))
69 c.navlist = navigation_list(self.request)
69 c.navlist = navigation_list(self.request)
70 self._register_global_c(c)
70
71 return c
71 return c
72
72
73 @classmethod
73 @classmethod
74 def _get_ui_settings(cls):
74 def _get_ui_settings(cls):
75 ret = RhodeCodeUi.query().all()
75 ret = RhodeCodeUi.query().all()
76
76
77 if not ret:
77 if not ret:
78 raise Exception('Could not get application ui settings !')
78 raise Exception('Could not get application ui settings !')
79 settings = {}
79 settings = {}
80 for each in ret:
80 for each in ret:
81 k = each.ui_key
81 k = each.ui_key
82 v = each.ui_value
82 v = each.ui_value
83 if k == '/':
83 if k == '/':
84 k = 'root_path'
84 k = 'root_path'
85
85
86 if k in ['push_ssl', 'publish', 'enabled']:
86 if k in ['push_ssl', 'publish', 'enabled']:
87 v = str2bool(v)
87 v = str2bool(v)
88
88
89 if k.find('.') != -1:
89 if k.find('.') != -1:
90 k = k.replace('.', '_')
90 k = k.replace('.', '_')
91
91
92 if each.ui_section in ['hooks', 'extensions']:
92 if each.ui_section in ['hooks', 'extensions']:
93 v = each.ui_active
93 v = each.ui_active
94
94
95 settings[each.ui_section + '_' + k] = v
95 settings[each.ui_section + '_' + k] = v
96 return settings
96 return settings
97
97
98 @classmethod
98 @classmethod
99 def _form_defaults(cls):
99 def _form_defaults(cls):
100 defaults = SettingsModel().get_all_settings()
100 defaults = SettingsModel().get_all_settings()
101 defaults.update(cls._get_ui_settings())
101 defaults.update(cls._get_ui_settings())
102
102
103 defaults.update({
103 defaults.update({
104 'new_svn_branch': '',
104 'new_svn_branch': '',
105 'new_svn_tag': '',
105 'new_svn_tag': '',
106 })
106 })
107 return defaults
107 return defaults
108
108
109 @LoginRequired()
109 @LoginRequired()
110 @HasPermissionAllDecorator('hg.admin')
110 @HasPermissionAllDecorator('hg.admin')
111 @view_config(
111 @view_config(
112 route_name='admin_settings_vcs', request_method='GET',
112 route_name='admin_settings_vcs', request_method='GET',
113 renderer='rhodecode:templates/admin/settings/settings.mako')
113 renderer='rhodecode:templates/admin/settings/settings.mako')
114 def settings_vcs(self):
114 def settings_vcs(self):
115 c = self.load_default_context()
115 c = self.load_default_context()
116 c.active = 'vcs'
116 c.active = 'vcs'
117 model = VcsSettingsModel()
117 model = VcsSettingsModel()
118 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
118 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
119 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
119 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
120
120
121 settings = self.request.registry.settings
121 settings = self.request.registry.settings
122 c.svn_proxy_generate_config = settings[generate_config]
122 c.svn_proxy_generate_config = settings[generate_config]
123
123
124 defaults = self._form_defaults()
124 defaults = self._form_defaults()
125
125
126 model.create_largeobjects_dirs_if_needed(defaults['paths_root_path'])
126 model.create_largeobjects_dirs_if_needed(defaults['paths_root_path'])
127
127
128 data = render('rhodecode:templates/admin/settings/settings.mako',
128 data = render('rhodecode:templates/admin/settings/settings.mako',
129 self._get_template_context(c), self.request)
129 self._get_template_context(c), self.request)
130 html = formencode.htmlfill.render(
130 html = formencode.htmlfill.render(
131 data,
131 data,
132 defaults=defaults,
132 defaults=defaults,
133 encoding="UTF-8",
133 encoding="UTF-8",
134 force_defaults=False
134 force_defaults=False
135 )
135 )
136 return Response(html)
136 return Response(html)
137
137
138 @LoginRequired()
138 @LoginRequired()
139 @HasPermissionAllDecorator('hg.admin')
139 @HasPermissionAllDecorator('hg.admin')
140 @CSRFRequired()
140 @CSRFRequired()
141 @view_config(
141 @view_config(
142 route_name='admin_settings_vcs_update', request_method='POST',
142 route_name='admin_settings_vcs_update', request_method='POST',
143 renderer='rhodecode:templates/admin/settings/settings.mako')
143 renderer='rhodecode:templates/admin/settings/settings.mako')
144 def settings_vcs_update(self):
144 def settings_vcs_update(self):
145 _ = self.request.translate
145 _ = self.request.translate
146 c = self.load_default_context()
146 c = self.load_default_context()
147 c.active = 'vcs'
147 c.active = 'vcs'
148
148
149 model = VcsSettingsModel()
149 model = VcsSettingsModel()
150 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
150 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
151 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
151 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
152
152
153 settings = self.request.registry.settings
153 settings = self.request.registry.settings
154 c.svn_proxy_generate_config = settings[generate_config]
154 c.svn_proxy_generate_config = settings[generate_config]
155
155
156 application_form = ApplicationUiSettingsForm()()
156 application_form = ApplicationUiSettingsForm(self.request.translate)()
157
157
158 try:
158 try:
159 form_result = application_form.to_python(dict(self.request.POST))
159 form_result = application_form.to_python(dict(self.request.POST))
160 except formencode.Invalid as errors:
160 except formencode.Invalid as errors:
161 h.flash(
161 h.flash(
162 _("Some form inputs contain invalid data."),
162 _("Some form inputs contain invalid data."),
163 category='error')
163 category='error')
164 data = render('rhodecode:templates/admin/settings/settings.mako',
164 data = render('rhodecode:templates/admin/settings/settings.mako',
165 self._get_template_context(c), self.request)
165 self._get_template_context(c), self.request)
166 html = formencode.htmlfill.render(
166 html = formencode.htmlfill.render(
167 data,
167 data,
168 defaults=errors.value,
168 defaults=errors.value,
169 errors=errors.error_dict or {},
169 errors=errors.error_dict or {},
170 prefix_error=False,
170 prefix_error=False,
171 encoding="UTF-8",
171 encoding="UTF-8",
172 force_defaults=False
172 force_defaults=False
173 )
173 )
174 return Response(html)
174 return Response(html)
175
175
176 try:
176 try:
177 if c.visual.allow_repo_location_change:
177 if c.visual.allow_repo_location_change:
178 model.update_global_path_setting(
178 model.update_global_path_setting(
179 form_result['paths_root_path'])
179 form_result['paths_root_path'])
180
180
181 model.update_global_ssl_setting(form_result['web_push_ssl'])
181 model.update_global_ssl_setting(form_result['web_push_ssl'])
182 model.update_global_hook_settings(form_result)
182 model.update_global_hook_settings(form_result)
183
183
184 model.create_or_update_global_svn_settings(form_result)
184 model.create_or_update_global_svn_settings(form_result)
185 model.create_or_update_global_hg_settings(form_result)
185 model.create_or_update_global_hg_settings(form_result)
186 model.create_or_update_global_git_settings(form_result)
186 model.create_or_update_global_git_settings(form_result)
187 model.create_or_update_global_pr_settings(form_result)
187 model.create_or_update_global_pr_settings(form_result)
188 except Exception:
188 except Exception:
189 log.exception("Exception while updating settings")
189 log.exception("Exception while updating settings")
190 h.flash(_('Error occurred during updating '
190 h.flash(_('Error occurred during updating '
191 'application settings'), category='error')
191 'application settings'), category='error')
192 else:
192 else:
193 Session().commit()
193 Session().commit()
194 h.flash(_('Updated VCS settings'), category='success')
194 h.flash(_('Updated VCS settings'), category='success')
195 raise HTTPFound(h.route_path('admin_settings_vcs'))
195 raise HTTPFound(h.route_path('admin_settings_vcs'))
196
196
197 data = render('rhodecode:templates/admin/settings/settings.mako',
197 data = render('rhodecode:templates/admin/settings/settings.mako',
198 self._get_template_context(c), self.request)
198 self._get_template_context(c), self.request)
199 html = formencode.htmlfill.render(
199 html = formencode.htmlfill.render(
200 data,
200 data,
201 defaults=self._form_defaults(),
201 defaults=self._form_defaults(),
202 encoding="UTF-8",
202 encoding="UTF-8",
203 force_defaults=False
203 force_defaults=False
204 )
204 )
205 return Response(html)
205 return Response(html)
206
206
207 @LoginRequired()
207 @LoginRequired()
208 @HasPermissionAllDecorator('hg.admin')
208 @HasPermissionAllDecorator('hg.admin')
209 @CSRFRequired()
209 @CSRFRequired()
210 @view_config(
210 @view_config(
211 route_name='admin_settings_vcs_svn_pattern_delete', request_method='POST',
211 route_name='admin_settings_vcs_svn_pattern_delete', request_method='POST',
212 renderer='json_ext', xhr=True)
212 renderer='json_ext', xhr=True)
213 def settings_vcs_delete_svn_pattern(self):
213 def settings_vcs_delete_svn_pattern(self):
214 delete_pattern_id = self.request.POST.get('delete_svn_pattern')
214 delete_pattern_id = self.request.POST.get('delete_svn_pattern')
215 model = VcsSettingsModel()
215 model = VcsSettingsModel()
216 try:
216 try:
217 model.delete_global_svn_pattern(delete_pattern_id)
217 model.delete_global_svn_pattern(delete_pattern_id)
218 except SettingNotFound:
218 except SettingNotFound:
219 log.exception(
219 log.exception(
220 'Failed to delete svn_pattern with id %s', delete_pattern_id)
220 'Failed to delete svn_pattern with id %s', delete_pattern_id)
221 raise HTTPNotFound()
221 raise HTTPNotFound()
222
222
223 Session().commit()
223 Session().commit()
224 return True
224 return True
225
225
226 @LoginRequired()
226 @LoginRequired()
227 @HasPermissionAllDecorator('hg.admin')
227 @HasPermissionAllDecorator('hg.admin')
228 @view_config(
228 @view_config(
229 route_name='admin_settings_mapping', request_method='GET',
229 route_name='admin_settings_mapping', request_method='GET',
230 renderer='rhodecode:templates/admin/settings/settings.mako')
230 renderer='rhodecode:templates/admin/settings/settings.mako')
231 def settings_mapping(self):
231 def settings_mapping(self):
232 c = self.load_default_context()
232 c = self.load_default_context()
233 c.active = 'mapping'
233 c.active = 'mapping'
234
234
235 data = render('rhodecode:templates/admin/settings/settings.mako',
235 data = render('rhodecode:templates/admin/settings/settings.mako',
236 self._get_template_context(c), self.request)
236 self._get_template_context(c), self.request)
237 html = formencode.htmlfill.render(
237 html = formencode.htmlfill.render(
238 data,
238 data,
239 defaults=self._form_defaults(),
239 defaults=self._form_defaults(),
240 encoding="UTF-8",
240 encoding="UTF-8",
241 force_defaults=False
241 force_defaults=False
242 )
242 )
243 return Response(html)
243 return Response(html)
244
244
245 @LoginRequired()
245 @LoginRequired()
246 @HasPermissionAllDecorator('hg.admin')
246 @HasPermissionAllDecorator('hg.admin')
247 @CSRFRequired()
247 @CSRFRequired()
248 @view_config(
248 @view_config(
249 route_name='admin_settings_mapping_update', request_method='POST',
249 route_name='admin_settings_mapping_update', request_method='POST',
250 renderer='rhodecode:templates/admin/settings/settings.mako')
250 renderer='rhodecode:templates/admin/settings/settings.mako')
251 def settings_mapping_update(self):
251 def settings_mapping_update(self):
252 _ = self.request.translate
252 _ = self.request.translate
253 c = self.load_default_context()
253 c = self.load_default_context()
254 c.active = 'mapping'
254 c.active = 'mapping'
255 rm_obsolete = self.request.POST.get('destroy', False)
255 rm_obsolete = self.request.POST.get('destroy', False)
256 invalidate_cache = self.request.POST.get('invalidate', False)
256 invalidate_cache = self.request.POST.get('invalidate', False)
257 log.debug(
257 log.debug(
258 'rescanning repo location with destroy obsolete=%s', rm_obsolete)
258 'rescanning repo location with destroy obsolete=%s', rm_obsolete)
259
259
260 if invalidate_cache:
260 if invalidate_cache:
261 log.debug('invalidating all repositories cache')
261 log.debug('invalidating all repositories cache')
262 for repo in Repository.get_all():
262 for repo in Repository.get_all():
263 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
263 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
264
264
265 filesystem_repos = ScmModel().repo_scan()
265 filesystem_repos = ScmModel().repo_scan()
266 added, removed = repo2db_mapper(filesystem_repos, rm_obsolete)
266 added, removed = repo2db_mapper(filesystem_repos, rm_obsolete)
267 _repr = lambda l: ', '.join(map(safe_unicode, l)) or '-'
267 _repr = lambda l: ', '.join(map(safe_unicode, l)) or '-'
268 h.flash(_('Repositories successfully '
268 h.flash(_('Repositories successfully '
269 'rescanned added: %s ; removed: %s') %
269 'rescanned added: %s ; removed: %s') %
270 (_repr(added), _repr(removed)),
270 (_repr(added), _repr(removed)),
271 category='success')
271 category='success')
272 raise HTTPFound(h.route_path('admin_settings_mapping'))
272 raise HTTPFound(h.route_path('admin_settings_mapping'))
273
273
274 @LoginRequired()
274 @LoginRequired()
275 @HasPermissionAllDecorator('hg.admin')
275 @HasPermissionAllDecorator('hg.admin')
276 @view_config(
276 @view_config(
277 route_name='admin_settings', request_method='GET',
277 route_name='admin_settings', request_method='GET',
278 renderer='rhodecode:templates/admin/settings/settings.mako')
278 renderer='rhodecode:templates/admin/settings/settings.mako')
279 @view_config(
279 @view_config(
280 route_name='admin_settings_global', request_method='GET',
280 route_name='admin_settings_global', request_method='GET',
281 renderer='rhodecode:templates/admin/settings/settings.mako')
281 renderer='rhodecode:templates/admin/settings/settings.mako')
282 def settings_global(self):
282 def settings_global(self):
283 c = self.load_default_context()
283 c = self.load_default_context()
284 c.active = 'global'
284 c.active = 'global'
285 c.personal_repo_group_default_pattern = RepoGroupModel()\
285 c.personal_repo_group_default_pattern = RepoGroupModel()\
286 .get_personal_group_name_pattern()
286 .get_personal_group_name_pattern()
287
287
288 data = render('rhodecode:templates/admin/settings/settings.mako',
288 data = render('rhodecode:templates/admin/settings/settings.mako',
289 self._get_template_context(c), self.request)
289 self._get_template_context(c), self.request)
290 html = formencode.htmlfill.render(
290 html = formencode.htmlfill.render(
291 data,
291 data,
292 defaults=self._form_defaults(),
292 defaults=self._form_defaults(),
293 encoding="UTF-8",
293 encoding="UTF-8",
294 force_defaults=False
294 force_defaults=False
295 )
295 )
296 return Response(html)
296 return Response(html)
297
297
298 @LoginRequired()
298 @LoginRequired()
299 @HasPermissionAllDecorator('hg.admin')
299 @HasPermissionAllDecorator('hg.admin')
300 @CSRFRequired()
300 @CSRFRequired()
301 @view_config(
301 @view_config(
302 route_name='admin_settings_update', request_method='POST',
302 route_name='admin_settings_update', request_method='POST',
303 renderer='rhodecode:templates/admin/settings/settings.mako')
303 renderer='rhodecode:templates/admin/settings/settings.mako')
304 @view_config(
304 @view_config(
305 route_name='admin_settings_global_update', request_method='POST',
305 route_name='admin_settings_global_update', request_method='POST',
306 renderer='rhodecode:templates/admin/settings/settings.mako')
306 renderer='rhodecode:templates/admin/settings/settings.mako')
307 def settings_global_update(self):
307 def settings_global_update(self):
308 _ = self.request.translate
308 _ = self.request.translate
309 c = self.load_default_context()
309 c = self.load_default_context()
310 c.active = 'global'
310 c.active = 'global'
311 c.personal_repo_group_default_pattern = RepoGroupModel()\
311 c.personal_repo_group_default_pattern = RepoGroupModel()\
312 .get_personal_group_name_pattern()
312 .get_personal_group_name_pattern()
313 application_form = ApplicationSettingsForm()()
313 application_form = ApplicationSettingsForm(self.request.translate)()
314 try:
314 try:
315 form_result = application_form.to_python(dict(self.request.POST))
315 form_result = application_form.to_python(dict(self.request.POST))
316 except formencode.Invalid as errors:
316 except formencode.Invalid as errors:
317 data = render('rhodecode:templates/admin/settings/settings.mako',
317 data = render('rhodecode:templates/admin/settings/settings.mako',
318 self._get_template_context(c), self.request)
318 self._get_template_context(c), self.request)
319 html = formencode.htmlfill.render(
319 html = formencode.htmlfill.render(
320 data,
320 data,
321 defaults=errors.value,
321 defaults=errors.value,
322 errors=errors.error_dict or {},
322 errors=errors.error_dict or {},
323 prefix_error=False,
323 prefix_error=False,
324 encoding="UTF-8",
324 encoding="UTF-8",
325 force_defaults=False
325 force_defaults=False
326 )
326 )
327 return Response(html)
327 return Response(html)
328
328
329 settings = [
329 settings = [
330 ('title', 'rhodecode_title', 'unicode'),
330 ('title', 'rhodecode_title', 'unicode'),
331 ('realm', 'rhodecode_realm', 'unicode'),
331 ('realm', 'rhodecode_realm', 'unicode'),
332 ('pre_code', 'rhodecode_pre_code', 'unicode'),
332 ('pre_code', 'rhodecode_pre_code', 'unicode'),
333 ('post_code', 'rhodecode_post_code', 'unicode'),
333 ('post_code', 'rhodecode_post_code', 'unicode'),
334 ('captcha_public_key', 'rhodecode_captcha_public_key', 'unicode'),
334 ('captcha_public_key', 'rhodecode_captcha_public_key', 'unicode'),
335 ('captcha_private_key', 'rhodecode_captcha_private_key', 'unicode'),
335 ('captcha_private_key', 'rhodecode_captcha_private_key', 'unicode'),
336 ('create_personal_repo_group', 'rhodecode_create_personal_repo_group', 'bool'),
336 ('create_personal_repo_group', 'rhodecode_create_personal_repo_group', 'bool'),
337 ('personal_repo_group_pattern', 'rhodecode_personal_repo_group_pattern', 'unicode'),
337 ('personal_repo_group_pattern', 'rhodecode_personal_repo_group_pattern', 'unicode'),
338 ]
338 ]
339 try:
339 try:
340 for setting, form_key, type_ in settings:
340 for setting, form_key, type_ in settings:
341 sett = SettingsModel().create_or_update_setting(
341 sett = SettingsModel().create_or_update_setting(
342 setting, form_result[form_key], type_)
342 setting, form_result[form_key], type_)
343 Session().add(sett)
343 Session().add(sett)
344
344
345 Session().commit()
345 Session().commit()
346 SettingsModel().invalidate_settings_cache()
346 SettingsModel().invalidate_settings_cache()
347 h.flash(_('Updated application settings'), category='success')
347 h.flash(_('Updated application settings'), category='success')
348 except Exception:
348 except Exception:
349 log.exception("Exception while updating application settings")
349 log.exception("Exception while updating application settings")
350 h.flash(
350 h.flash(
351 _('Error occurred during updating application settings'),
351 _('Error occurred during updating application settings'),
352 category='error')
352 category='error')
353
353
354 raise HTTPFound(h.route_path('admin_settings_global'))
354 raise HTTPFound(h.route_path('admin_settings_global'))
355
355
356 @LoginRequired()
356 @LoginRequired()
357 @HasPermissionAllDecorator('hg.admin')
357 @HasPermissionAllDecorator('hg.admin')
358 @view_config(
358 @view_config(
359 route_name='admin_settings_visual', request_method='GET',
359 route_name='admin_settings_visual', request_method='GET',
360 renderer='rhodecode:templates/admin/settings/settings.mako')
360 renderer='rhodecode:templates/admin/settings/settings.mako')
361 def settings_visual(self):
361 def settings_visual(self):
362 c = self.load_default_context()
362 c = self.load_default_context()
363 c.active = 'visual'
363 c.active = 'visual'
364
364
365 data = render('rhodecode:templates/admin/settings/settings.mako',
365 data = render('rhodecode:templates/admin/settings/settings.mako',
366 self._get_template_context(c), self.request)
366 self._get_template_context(c), self.request)
367 html = formencode.htmlfill.render(
367 html = formencode.htmlfill.render(
368 data,
368 data,
369 defaults=self._form_defaults(),
369 defaults=self._form_defaults(),
370 encoding="UTF-8",
370 encoding="UTF-8",
371 force_defaults=False
371 force_defaults=False
372 )
372 )
373 return Response(html)
373 return Response(html)
374
374
375 @LoginRequired()
375 @LoginRequired()
376 @HasPermissionAllDecorator('hg.admin')
376 @HasPermissionAllDecorator('hg.admin')
377 @CSRFRequired()
377 @CSRFRequired()
378 @view_config(
378 @view_config(
379 route_name='admin_settings_visual_update', request_method='POST',
379 route_name='admin_settings_visual_update', request_method='POST',
380 renderer='rhodecode:templates/admin/settings/settings.mako')
380 renderer='rhodecode:templates/admin/settings/settings.mako')
381 def settings_visual_update(self):
381 def settings_visual_update(self):
382 _ = self.request.translate
382 _ = self.request.translate
383 c = self.load_default_context()
383 c = self.load_default_context()
384 c.active = 'visual'
384 c.active = 'visual'
385 application_form = ApplicationVisualisationForm()()
385 application_form = ApplicationVisualisationForm(self.request.translate)()
386 try:
386 try:
387 form_result = application_form.to_python(dict(self.request.POST))
387 form_result = application_form.to_python(dict(self.request.POST))
388 except formencode.Invalid as errors:
388 except formencode.Invalid as errors:
389 data = render('rhodecode:templates/admin/settings/settings.mako',
389 data = render('rhodecode:templates/admin/settings/settings.mako',
390 self._get_template_context(c), self.request)
390 self._get_template_context(c), self.request)
391 html = formencode.htmlfill.render(
391 html = formencode.htmlfill.render(
392 data,
392 data,
393 defaults=errors.value,
393 defaults=errors.value,
394 errors=errors.error_dict or {},
394 errors=errors.error_dict or {},
395 prefix_error=False,
395 prefix_error=False,
396 encoding="UTF-8",
396 encoding="UTF-8",
397 force_defaults=False
397 force_defaults=False
398 )
398 )
399 return Response(html)
399 return Response(html)
400
400
401 try:
401 try:
402 settings = [
402 settings = [
403 ('show_public_icon', 'rhodecode_show_public_icon', 'bool'),
403 ('show_public_icon', 'rhodecode_show_public_icon', 'bool'),
404 ('show_private_icon', 'rhodecode_show_private_icon', 'bool'),
404 ('show_private_icon', 'rhodecode_show_private_icon', 'bool'),
405 ('stylify_metatags', 'rhodecode_stylify_metatags', 'bool'),
405 ('stylify_metatags', 'rhodecode_stylify_metatags', 'bool'),
406 ('repository_fields', 'rhodecode_repository_fields', 'bool'),
406 ('repository_fields', 'rhodecode_repository_fields', 'bool'),
407 ('dashboard_items', 'rhodecode_dashboard_items', 'int'),
407 ('dashboard_items', 'rhodecode_dashboard_items', 'int'),
408 ('admin_grid_items', 'rhodecode_admin_grid_items', 'int'),
408 ('admin_grid_items', 'rhodecode_admin_grid_items', 'int'),
409 ('show_version', 'rhodecode_show_version', 'bool'),
409 ('show_version', 'rhodecode_show_version', 'bool'),
410 ('use_gravatar', 'rhodecode_use_gravatar', 'bool'),
410 ('use_gravatar', 'rhodecode_use_gravatar', 'bool'),
411 ('markup_renderer', 'rhodecode_markup_renderer', 'unicode'),
411 ('markup_renderer', 'rhodecode_markup_renderer', 'unicode'),
412 ('gravatar_url', 'rhodecode_gravatar_url', 'unicode'),
412 ('gravatar_url', 'rhodecode_gravatar_url', 'unicode'),
413 ('clone_uri_tmpl', 'rhodecode_clone_uri_tmpl', 'unicode'),
413 ('clone_uri_tmpl', 'rhodecode_clone_uri_tmpl', 'unicode'),
414 ('support_url', 'rhodecode_support_url', 'unicode'),
414 ('support_url', 'rhodecode_support_url', 'unicode'),
415 ('show_revision_number', 'rhodecode_show_revision_number', 'bool'),
415 ('show_revision_number', 'rhodecode_show_revision_number', 'bool'),
416 ('show_sha_length', 'rhodecode_show_sha_length', 'int'),
416 ('show_sha_length', 'rhodecode_show_sha_length', 'int'),
417 ]
417 ]
418 for setting, form_key, type_ in settings:
418 for setting, form_key, type_ in settings:
419 sett = SettingsModel().create_or_update_setting(
419 sett = SettingsModel().create_or_update_setting(
420 setting, form_result[form_key], type_)
420 setting, form_result[form_key], type_)
421 Session().add(sett)
421 Session().add(sett)
422
422
423 Session().commit()
423 Session().commit()
424 SettingsModel().invalidate_settings_cache()
424 SettingsModel().invalidate_settings_cache()
425 h.flash(_('Updated visualisation settings'), category='success')
425 h.flash(_('Updated visualisation settings'), category='success')
426 except Exception:
426 except Exception:
427 log.exception("Exception updating visualization settings")
427 log.exception("Exception updating visualization settings")
428 h.flash(_('Error occurred during updating '
428 h.flash(_('Error occurred during updating '
429 'visualisation settings'),
429 'visualisation settings'),
430 category='error')
430 category='error')
431
431
432 raise HTTPFound(h.route_path('admin_settings_visual'))
432 raise HTTPFound(h.route_path('admin_settings_visual'))
433
433
434 @LoginRequired()
434 @LoginRequired()
435 @HasPermissionAllDecorator('hg.admin')
435 @HasPermissionAllDecorator('hg.admin')
436 @view_config(
436 @view_config(
437 route_name='admin_settings_issuetracker', request_method='GET',
437 route_name='admin_settings_issuetracker', request_method='GET',
438 renderer='rhodecode:templates/admin/settings/settings.mako')
438 renderer='rhodecode:templates/admin/settings/settings.mako')
439 def settings_issuetracker(self):
439 def settings_issuetracker(self):
440 c = self.load_default_context()
440 c = self.load_default_context()
441 c.active = 'issuetracker'
441 c.active = 'issuetracker'
442 defaults = SettingsModel().get_all_settings()
442 defaults = SettingsModel().get_all_settings()
443
443
444 entry_key = 'rhodecode_issuetracker_pat_'
444 entry_key = 'rhodecode_issuetracker_pat_'
445
445
446 c.issuetracker_entries = {}
446 c.issuetracker_entries = {}
447 for k, v in defaults.items():
447 for k, v in defaults.items():
448 if k.startswith(entry_key):
448 if k.startswith(entry_key):
449 uid = k[len(entry_key):]
449 uid = k[len(entry_key):]
450 c.issuetracker_entries[uid] = None
450 c.issuetracker_entries[uid] = None
451
451
452 for uid in c.issuetracker_entries:
452 for uid in c.issuetracker_entries:
453 c.issuetracker_entries[uid] = AttributeDict({
453 c.issuetracker_entries[uid] = AttributeDict({
454 'pat': defaults.get('rhodecode_issuetracker_pat_' + uid),
454 'pat': defaults.get('rhodecode_issuetracker_pat_' + uid),
455 'url': defaults.get('rhodecode_issuetracker_url_' + uid),
455 'url': defaults.get('rhodecode_issuetracker_url_' + uid),
456 'pref': defaults.get('rhodecode_issuetracker_pref_' + uid),
456 'pref': defaults.get('rhodecode_issuetracker_pref_' + uid),
457 'desc': defaults.get('rhodecode_issuetracker_desc_' + uid),
457 'desc': defaults.get('rhodecode_issuetracker_desc_' + uid),
458 })
458 })
459
459
460 return self._get_template_context(c)
460 return self._get_template_context(c)
461
461
462 @LoginRequired()
462 @LoginRequired()
463 @HasPermissionAllDecorator('hg.admin')
463 @HasPermissionAllDecorator('hg.admin')
464 @CSRFRequired()
464 @CSRFRequired()
465 @view_config(
465 @view_config(
466 route_name='admin_settings_issuetracker_test', request_method='POST',
466 route_name='admin_settings_issuetracker_test', request_method='POST',
467 renderer='string', xhr=True)
467 renderer='string', xhr=True)
468 def settings_issuetracker_test(self):
468 def settings_issuetracker_test(self):
469 return h.urlify_commit_message(
469 return h.urlify_commit_message(
470 self.request.POST.get('test_text', ''),
470 self.request.POST.get('test_text', ''),
471 'repo_group/test_repo1')
471 'repo_group/test_repo1')
472
472
473 @LoginRequired()
473 @LoginRequired()
474 @HasPermissionAllDecorator('hg.admin')
474 @HasPermissionAllDecorator('hg.admin')
475 @CSRFRequired()
475 @CSRFRequired()
476 @view_config(
476 @view_config(
477 route_name='admin_settings_issuetracker_update', request_method='POST',
477 route_name='admin_settings_issuetracker_update', request_method='POST',
478 renderer='rhodecode:templates/admin/settings/settings.mako')
478 renderer='rhodecode:templates/admin/settings/settings.mako')
479 def settings_issuetracker_update(self):
479 def settings_issuetracker_update(self):
480 _ = self.request.translate
480 _ = self.request.translate
481 self.load_default_context()
481 self.load_default_context()
482 settings_model = IssueTrackerSettingsModel()
482 settings_model = IssueTrackerSettingsModel()
483
483
484 try:
484 try:
485 form = IssueTrackerPatternsForm()().to_python(self.request.POST)
485 form = IssueTrackerPatternsForm(self.request.translate)().to_python(self.request.POST)
486 except formencode.Invalid as errors:
486 except formencode.Invalid as errors:
487 log.exception('Failed to add new pattern')
487 log.exception('Failed to add new pattern')
488 error = errors
488 error = errors
489 h.flash(_('Invalid issue tracker pattern: {}'.format(error)),
489 h.flash(_('Invalid issue tracker pattern: {}'.format(error)),
490 category='error')
490 category='error')
491 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
491 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
492
492
493 if form:
493 if form:
494 for uid in form.get('delete_patterns', []):
494 for uid in form.get('delete_patterns', []):
495 settings_model.delete_entries(uid)
495 settings_model.delete_entries(uid)
496
496
497 for pattern in form.get('patterns', []):
497 for pattern in form.get('patterns', []):
498 for setting, value, type_ in pattern:
498 for setting, value, type_ in pattern:
499 sett = settings_model.create_or_update_setting(
499 sett = settings_model.create_or_update_setting(
500 setting, value, type_)
500 setting, value, type_)
501 Session().add(sett)
501 Session().add(sett)
502
502
503 Session().commit()
503 Session().commit()
504
504
505 SettingsModel().invalidate_settings_cache()
505 SettingsModel().invalidate_settings_cache()
506 h.flash(_('Updated issue tracker entries'), category='success')
506 h.flash(_('Updated issue tracker entries'), category='success')
507 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
507 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
508
508
509 @LoginRequired()
509 @LoginRequired()
510 @HasPermissionAllDecorator('hg.admin')
510 @HasPermissionAllDecorator('hg.admin')
511 @CSRFRequired()
511 @CSRFRequired()
512 @view_config(
512 @view_config(
513 route_name='admin_settings_issuetracker_delete', request_method='POST',
513 route_name='admin_settings_issuetracker_delete', request_method='POST',
514 renderer='rhodecode:templates/admin/settings/settings.mako')
514 renderer='rhodecode:templates/admin/settings/settings.mako')
515 def settings_issuetracker_delete(self):
515 def settings_issuetracker_delete(self):
516 _ = self.request.translate
516 _ = self.request.translate
517 self.load_default_context()
517 self.load_default_context()
518 uid = self.request.POST.get('uid')
518 uid = self.request.POST.get('uid')
519 try:
519 try:
520 IssueTrackerSettingsModel().delete_entries(uid)
520 IssueTrackerSettingsModel().delete_entries(uid)
521 except Exception:
521 except Exception:
522 log.exception('Failed to delete issue tracker setting %s', uid)
522 log.exception('Failed to delete issue tracker setting %s', uid)
523 raise HTTPNotFound()
523 raise HTTPNotFound()
524 h.flash(_('Removed issue tracker entry'), category='success')
524 h.flash(_('Removed issue tracker entry'), category='success')
525 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
525 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
526
526
527 @LoginRequired()
527 @LoginRequired()
528 @HasPermissionAllDecorator('hg.admin')
528 @HasPermissionAllDecorator('hg.admin')
529 @view_config(
529 @view_config(
530 route_name='admin_settings_email', request_method='GET',
530 route_name='admin_settings_email', request_method='GET',
531 renderer='rhodecode:templates/admin/settings/settings.mako')
531 renderer='rhodecode:templates/admin/settings/settings.mako')
532 def settings_email(self):
532 def settings_email(self):
533 c = self.load_default_context()
533 c = self.load_default_context()
534 c.active = 'email'
534 c.active = 'email'
535 c.rhodecode_ini = rhodecode.CONFIG
535 c.rhodecode_ini = rhodecode.CONFIG
536
536
537 data = render('rhodecode:templates/admin/settings/settings.mako',
537 data = render('rhodecode:templates/admin/settings/settings.mako',
538 self._get_template_context(c), self.request)
538 self._get_template_context(c), self.request)
539 html = formencode.htmlfill.render(
539 html = formencode.htmlfill.render(
540 data,
540 data,
541 defaults=self._form_defaults(),
541 defaults=self._form_defaults(),
542 encoding="UTF-8",
542 encoding="UTF-8",
543 force_defaults=False
543 force_defaults=False
544 )
544 )
545 return Response(html)
545 return Response(html)
546
546
547 @LoginRequired()
547 @LoginRequired()
548 @HasPermissionAllDecorator('hg.admin')
548 @HasPermissionAllDecorator('hg.admin')
549 @CSRFRequired()
549 @CSRFRequired()
550 @view_config(
550 @view_config(
551 route_name='admin_settings_email_update', request_method='POST',
551 route_name='admin_settings_email_update', request_method='POST',
552 renderer='rhodecode:templates/admin/settings/settings.mako')
552 renderer='rhodecode:templates/admin/settings/settings.mako')
553 def settings_email_update(self):
553 def settings_email_update(self):
554 _ = self.request.translate
554 _ = self.request.translate
555 c = self.load_default_context()
555 c = self.load_default_context()
556 c.active = 'email'
556 c.active = 'email'
557
557
558 test_email = self.request.POST.get('test_email')
558 test_email = self.request.POST.get('test_email')
559
559
560 if not test_email:
560 if not test_email:
561 h.flash(_('Please enter email address'), category='error')
561 h.flash(_('Please enter email address'), category='error')
562 raise HTTPFound(h.route_path('admin_settings_email'))
562 raise HTTPFound(h.route_path('admin_settings_email'))
563
563
564 email_kwargs = {
564 email_kwargs = {
565 'date': datetime.datetime.now(),
565 'date': datetime.datetime.now(),
566 'user': c.rhodecode_user,
566 'user': c.rhodecode_user,
567 'rhodecode_version': c.rhodecode_version
567 'rhodecode_version': c.rhodecode_version
568 }
568 }
569
569
570 (subject, headers, email_body,
570 (subject, headers, email_body,
571 email_body_plaintext) = EmailNotificationModel().render_email(
571 email_body_plaintext) = EmailNotificationModel().render_email(
572 EmailNotificationModel.TYPE_EMAIL_TEST, **email_kwargs)
572 EmailNotificationModel.TYPE_EMAIL_TEST, **email_kwargs)
573
573
574 recipients = [test_email] if test_email else None
574 recipients = [test_email] if test_email else None
575
575
576 run_task(tasks.send_email, recipients, subject,
576 run_task(tasks.send_email, recipients, subject,
577 email_body_plaintext, email_body)
577 email_body_plaintext, email_body)
578
578
579 h.flash(_('Send email task created'), category='success')
579 h.flash(_('Send email task created'), category='success')
580 raise HTTPFound(h.route_path('admin_settings_email'))
580 raise HTTPFound(h.route_path('admin_settings_email'))
581
581
582 @LoginRequired()
582 @LoginRequired()
583 @HasPermissionAllDecorator('hg.admin')
583 @HasPermissionAllDecorator('hg.admin')
584 @view_config(
584 @view_config(
585 route_name='admin_settings_hooks', request_method='GET',
585 route_name='admin_settings_hooks', request_method='GET',
586 renderer='rhodecode:templates/admin/settings/settings.mako')
586 renderer='rhodecode:templates/admin/settings/settings.mako')
587 def settings_hooks(self):
587 def settings_hooks(self):
588 c = self.load_default_context()
588 c = self.load_default_context()
589 c.active = 'hooks'
589 c.active = 'hooks'
590
590
591 model = SettingsModel()
591 model = SettingsModel()
592 c.hooks = model.get_builtin_hooks()
592 c.hooks = model.get_builtin_hooks()
593 c.custom_hooks = model.get_custom_hooks()
593 c.custom_hooks = model.get_custom_hooks()
594
594
595 data = render('rhodecode:templates/admin/settings/settings.mako',
595 data = render('rhodecode:templates/admin/settings/settings.mako',
596 self._get_template_context(c), self.request)
596 self._get_template_context(c), self.request)
597 html = formencode.htmlfill.render(
597 html = formencode.htmlfill.render(
598 data,
598 data,
599 defaults=self._form_defaults(),
599 defaults=self._form_defaults(),
600 encoding="UTF-8",
600 encoding="UTF-8",
601 force_defaults=False
601 force_defaults=False
602 )
602 )
603 return Response(html)
603 return Response(html)
604
604
605 @LoginRequired()
605 @LoginRequired()
606 @HasPermissionAllDecorator('hg.admin')
606 @HasPermissionAllDecorator('hg.admin')
607 @CSRFRequired()
607 @CSRFRequired()
608 @view_config(
608 @view_config(
609 route_name='admin_settings_hooks_update', request_method='POST',
609 route_name='admin_settings_hooks_update', request_method='POST',
610 renderer='rhodecode:templates/admin/settings/settings.mako')
610 renderer='rhodecode:templates/admin/settings/settings.mako')
611 @view_config(
611 @view_config(
612 route_name='admin_settings_hooks_delete', request_method='POST',
612 route_name='admin_settings_hooks_delete', request_method='POST',
613 renderer='rhodecode:templates/admin/settings/settings.mako')
613 renderer='rhodecode:templates/admin/settings/settings.mako')
614 def settings_hooks_update(self):
614 def settings_hooks_update(self):
615 _ = self.request.translate
615 _ = self.request.translate
616 c = self.load_default_context()
616 c = self.load_default_context()
617 c.active = 'hooks'
617 c.active = 'hooks'
618 if c.visual.allow_custom_hooks_settings:
618 if c.visual.allow_custom_hooks_settings:
619 ui_key = self.request.POST.get('new_hook_ui_key')
619 ui_key = self.request.POST.get('new_hook_ui_key')
620 ui_value = self.request.POST.get('new_hook_ui_value')
620 ui_value = self.request.POST.get('new_hook_ui_value')
621
621
622 hook_id = self.request.POST.get('hook_id')
622 hook_id = self.request.POST.get('hook_id')
623 new_hook = False
623 new_hook = False
624
624
625 model = SettingsModel()
625 model = SettingsModel()
626 try:
626 try:
627 if ui_value and ui_key:
627 if ui_value and ui_key:
628 model.create_or_update_hook(ui_key, ui_value)
628 model.create_or_update_hook(ui_key, ui_value)
629 h.flash(_('Added new hook'), category='success')
629 h.flash(_('Added new hook'), category='success')
630 new_hook = True
630 new_hook = True
631 elif hook_id:
631 elif hook_id:
632 RhodeCodeUi.delete(hook_id)
632 RhodeCodeUi.delete(hook_id)
633 Session().commit()
633 Session().commit()
634
634
635 # check for edits
635 # check for edits
636 update = False
636 update = False
637 _d = self.request.POST.dict_of_lists()
637 _d = self.request.POST.dict_of_lists()
638 for k, v in zip(_d.get('hook_ui_key', []),
638 for k, v in zip(_d.get('hook_ui_key', []),
639 _d.get('hook_ui_value_new', [])):
639 _d.get('hook_ui_value_new', [])):
640 model.create_or_update_hook(k, v)
640 model.create_or_update_hook(k, v)
641 update = True
641 update = True
642
642
643 if update and not new_hook:
643 if update and not new_hook:
644 h.flash(_('Updated hooks'), category='success')
644 h.flash(_('Updated hooks'), category='success')
645 Session().commit()
645 Session().commit()
646 except Exception:
646 except Exception:
647 log.exception("Exception during hook creation")
647 log.exception("Exception during hook creation")
648 h.flash(_('Error occurred during hook creation'),
648 h.flash(_('Error occurred during hook creation'),
649 category='error')
649 category='error')
650
650
651 raise HTTPFound(h.route_path('admin_settings_hooks'))
651 raise HTTPFound(h.route_path('admin_settings_hooks'))
652
652
653 @LoginRequired()
653 @LoginRequired()
654 @HasPermissionAllDecorator('hg.admin')
654 @HasPermissionAllDecorator('hg.admin')
655 @view_config(
655 @view_config(
656 route_name='admin_settings_search', request_method='GET',
656 route_name='admin_settings_search', request_method='GET',
657 renderer='rhodecode:templates/admin/settings/settings.mako')
657 renderer='rhodecode:templates/admin/settings/settings.mako')
658 def settings_search(self):
658 def settings_search(self):
659 c = self.load_default_context()
659 c = self.load_default_context()
660 c.active = 'search'
660 c.active = 'search'
661
661
662 searcher = searcher_from_config(self.request.registry.settings)
662 searcher = searcher_from_config(self.request.registry.settings)
663 c.statistics = searcher.statistics()
663 c.statistics = searcher.statistics()
664
664
665 return self._get_template_context(c)
665 return self._get_template_context(c)
666
666
667 @LoginRequired()
667 @LoginRequired()
668 @HasPermissionAllDecorator('hg.admin')
668 @HasPermissionAllDecorator('hg.admin')
669 @view_config(
669 @view_config(
670 route_name='admin_settings_labs', request_method='GET',
670 route_name='admin_settings_labs', request_method='GET',
671 renderer='rhodecode:templates/admin/settings/settings.mako')
671 renderer='rhodecode:templates/admin/settings/settings.mako')
672 def settings_labs(self):
672 def settings_labs(self):
673 c = self.load_default_context()
673 c = self.load_default_context()
674 if not c.labs_active:
674 if not c.labs_active:
675 raise HTTPFound(h.route_path('admin_settings'))
675 raise HTTPFound(h.route_path('admin_settings'))
676
676
677 c.active = 'labs'
677 c.active = 'labs'
678 c.lab_settings = _LAB_SETTINGS
678 c.lab_settings = _LAB_SETTINGS
679
679
680 data = render('rhodecode:templates/admin/settings/settings.mako',
680 data = render('rhodecode:templates/admin/settings/settings.mako',
681 self._get_template_context(c), self.request)
681 self._get_template_context(c), self.request)
682 html = formencode.htmlfill.render(
682 html = formencode.htmlfill.render(
683 data,
683 data,
684 defaults=self._form_defaults(),
684 defaults=self._form_defaults(),
685 encoding="UTF-8",
685 encoding="UTF-8",
686 force_defaults=False
686 force_defaults=False
687 )
687 )
688 return Response(html)
688 return Response(html)
689
689
690 @LoginRequired()
690 @LoginRequired()
691 @HasPermissionAllDecorator('hg.admin')
691 @HasPermissionAllDecorator('hg.admin')
692 @CSRFRequired()
692 @CSRFRequired()
693 @view_config(
693 @view_config(
694 route_name='admin_settings_labs_update', request_method='POST',
694 route_name='admin_settings_labs_update', request_method='POST',
695 renderer='rhodecode:templates/admin/settings/settings.mako')
695 renderer='rhodecode:templates/admin/settings/settings.mako')
696 def settings_labs_update(self):
696 def settings_labs_update(self):
697 _ = self.request.translate
697 _ = self.request.translate
698 c = self.load_default_context()
698 c = self.load_default_context()
699 c.active = 'labs'
699 c.active = 'labs'
700
700
701 application_form = LabsSettingsForm()()
701 application_form = LabsSettingsForm(self.request.translate)()
702 try:
702 try:
703 form_result = application_form.to_python(dict(self.request.POST))
703 form_result = application_form.to_python(dict(self.request.POST))
704 except formencode.Invalid as errors:
704 except formencode.Invalid as errors:
705 h.flash(
705 h.flash(
706 _('Some form inputs contain invalid data.'),
706 _('Some form inputs contain invalid data.'),
707 category='error')
707 category='error')
708 data = render('rhodecode:templates/admin/settings/settings.mako',
708 data = render('rhodecode:templates/admin/settings/settings.mako',
709 self._get_template_context(c), self.request)
709 self._get_template_context(c), self.request)
710 html = formencode.htmlfill.render(
710 html = formencode.htmlfill.render(
711 data,
711 data,
712 defaults=errors.value,
712 defaults=errors.value,
713 errors=errors.error_dict or {},
713 errors=errors.error_dict or {},
714 prefix_error=False,
714 prefix_error=False,
715 encoding="UTF-8",
715 encoding="UTF-8",
716 force_defaults=False
716 force_defaults=False
717 )
717 )
718 return Response(html)
718 return Response(html)
719
719
720 try:
720 try:
721 session = Session()
721 session = Session()
722 for setting in _LAB_SETTINGS:
722 for setting in _LAB_SETTINGS:
723 setting_name = setting.key[len('rhodecode_'):]
723 setting_name = setting.key[len('rhodecode_'):]
724 sett = SettingsModel().create_or_update_setting(
724 sett = SettingsModel().create_or_update_setting(
725 setting_name, form_result[setting.key], setting.type)
725 setting_name, form_result[setting.key], setting.type)
726 session.add(sett)
726 session.add(sett)
727
727
728 except Exception:
728 except Exception:
729 log.exception('Exception while updating lab settings')
729 log.exception('Exception while updating lab settings')
730 h.flash(_('Error occurred during updating labs settings'),
730 h.flash(_('Error occurred during updating labs settings'),
731 category='error')
731 category='error')
732 else:
732 else:
733 Session().commit()
733 Session().commit()
734 SettingsModel().invalidate_settings_cache()
734 SettingsModel().invalidate_settings_cache()
735 h.flash(_('Updated Labs settings'), category='success')
735 h.flash(_('Updated Labs settings'), category='success')
736 raise HTTPFound(h.route_path('admin_settings_labs'))
736 raise HTTPFound(h.route_path('admin_settings_labs'))
737
737
738 data = render('rhodecode:templates/admin/settings/settings.mako',
738 data = render('rhodecode:templates/admin/settings/settings.mako',
739 self._get_template_context(c), self.request)
739 self._get_template_context(c), self.request)
740 html = formencode.htmlfill.render(
740 html = formencode.htmlfill.render(
741 data,
741 data,
742 defaults=self._form_defaults(),
742 defaults=self._form_defaults(),
743 encoding="UTF-8",
743 encoding="UTF-8",
744 force_defaults=False
744 force_defaults=False
745 )
745 )
746 return Response(html)
746 return Response(html)
747
747
748
748
749 # :param key: name of the setting including the 'rhodecode_' prefix
749 # :param key: name of the setting including the 'rhodecode_' prefix
750 # :param type: the RhodeCodeSetting type to use.
750 # :param type: the RhodeCodeSetting type to use.
751 # :param group: the i18ned group in which we should dispaly this setting
751 # :param group: the i18ned group in which we should dispaly this setting
752 # :param label: the i18ned label we should display for this setting
752 # :param label: the i18ned label we should display for this setting
753 # :param help: the i18ned help we should dispaly for this setting
753 # :param help: the i18ned help we should dispaly for this setting
754 LabSetting = collections.namedtuple(
754 LabSetting = collections.namedtuple(
755 'LabSetting', ('key', 'type', 'group', 'label', 'help'))
755 'LabSetting', ('key', 'type', 'group', 'label', 'help'))
756
756
757
757
758 # This list has to be kept in sync with the form
758 # This list has to be kept in sync with the form
759 # rhodecode.model.forms.LabsSettingsForm.
759 # rhodecode.model.forms.LabsSettingsForm.
760 _LAB_SETTINGS = [
760 _LAB_SETTINGS = [
761
761
762 ]
762 ]
@@ -1,209 +1,209 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 import urllib2
22 import urllib2
23 import packaging.version
23 import packaging.version
24
24
25 from pyramid.view import view_config
25 from pyramid.view import view_config
26
26
27 import rhodecode
27 import rhodecode
28 from rhodecode.apps._base import BaseAppView
28 from rhodecode.apps._base import BaseAppView
29 from rhodecode.apps.admin.navigation import navigation_list
29 from rhodecode.apps.admin.navigation import navigation_list
30 from rhodecode.lib import helpers as h
30 from rhodecode.lib import helpers as h
31 from rhodecode.lib.auth import (LoginRequired, HasPermissionAllDecorator)
31 from rhodecode.lib.auth import (LoginRequired, HasPermissionAllDecorator)
32 from rhodecode.lib.utils2 import str2bool
32 from rhodecode.lib.utils2 import str2bool
33 from rhodecode.lib import system_info
33 from rhodecode.lib import system_info
34 from rhodecode.lib.ext_json import json
34 from rhodecode.lib.ext_json import json
35 from rhodecode.model.settings import SettingsModel
35 from rhodecode.model.settings import SettingsModel
36
36
37 log = logging.getLogger(__name__)
37 log = logging.getLogger(__name__)
38
38
39
39
40 class AdminSystemInfoSettingsView(BaseAppView):
40 class AdminSystemInfoSettingsView(BaseAppView):
41 def load_default_context(self):
41 def load_default_context(self):
42 c = self._get_local_tmpl_context()
42 c = self._get_local_tmpl_context()
43 self._register_global_c(c)
43
44 return c
44 return c
45
45
46 @staticmethod
46 @staticmethod
47 def get_update_data(update_url):
47 def get_update_data(update_url):
48 """Return the JSON update data."""
48 """Return the JSON update data."""
49 ver = rhodecode.__version__
49 ver = rhodecode.__version__
50 log.debug('Checking for upgrade on `%s` server', update_url)
50 log.debug('Checking for upgrade on `%s` server', update_url)
51 opener = urllib2.build_opener()
51 opener = urllib2.build_opener()
52 opener.addheaders = [('User-agent', 'RhodeCode-SCM/%s' % ver)]
52 opener.addheaders = [('User-agent', 'RhodeCode-SCM/%s' % ver)]
53 response = opener.open(update_url)
53 response = opener.open(update_url)
54 response_data = response.read()
54 response_data = response.read()
55 data = json.loads(response_data)
55 data = json.loads(response_data)
56
56
57 return data
57 return data
58
58
59 def get_update_url(self):
59 def get_update_url(self):
60 settings = SettingsModel().get_all_settings()
60 settings = SettingsModel().get_all_settings()
61 return settings.get('rhodecode_update_url')
61 return settings.get('rhodecode_update_url')
62
62
63 @LoginRequired()
63 @LoginRequired()
64 @HasPermissionAllDecorator('hg.admin')
64 @HasPermissionAllDecorator('hg.admin')
65 @view_config(
65 @view_config(
66 route_name='admin_settings_system', request_method='GET',
66 route_name='admin_settings_system', request_method='GET',
67 renderer='rhodecode:templates/admin/settings/settings.mako')
67 renderer='rhodecode:templates/admin/settings/settings.mako')
68 def settings_system_info(self):
68 def settings_system_info(self):
69 _ = self.request.translate
69 _ = self.request.translate
70 c = self.load_default_context()
70 c = self.load_default_context()
71
71
72 c.active = 'system'
72 c.active = 'system'
73 c.navlist = navigation_list(self.request)
73 c.navlist = navigation_list(self.request)
74
74
75 # TODO(marcink), figure out how to allow only selected users to do this
75 # TODO(marcink), figure out how to allow only selected users to do this
76 c.allowed_to_snapshot = self._rhodecode_user.admin
76 c.allowed_to_snapshot = self._rhodecode_user.admin
77
77
78 snapshot = str2bool(self.request.params.get('snapshot'))
78 snapshot = str2bool(self.request.params.get('snapshot'))
79
79
80 c.rhodecode_update_url = self.get_update_url()
80 c.rhodecode_update_url = self.get_update_url()
81 server_info = system_info.get_system_info(self.request.environ)
81 server_info = system_info.get_system_info(self.request.environ)
82
82
83 for key, val in server_info.items():
83 for key, val in server_info.items():
84 setattr(c, key, val)
84 setattr(c, key, val)
85
85
86 def val(name, subkey='human_value'):
86 def val(name, subkey='human_value'):
87 return server_info[name][subkey]
87 return server_info[name][subkey]
88
88
89 def state(name):
89 def state(name):
90 return server_info[name]['state']
90 return server_info[name]['state']
91
91
92 def val2(name):
92 def val2(name):
93 val = server_info[name]['human_value']
93 val = server_info[name]['human_value']
94 state = server_info[name]['state']
94 state = server_info[name]['state']
95 return val, state
95 return val, state
96
96
97 update_info_msg = _('Note: please make sure this server can '
97 update_info_msg = _('Note: please make sure this server can '
98 'access `${url}` for the update link to work',
98 'access `${url}` for the update link to work',
99 mapping=dict(url=c.rhodecode_update_url))
99 mapping=dict(url=c.rhodecode_update_url))
100 c.data_items = [
100 c.data_items = [
101 # update info
101 # update info
102 (_('Update info'), h.literal(
102 (_('Update info'), h.literal(
103 '<span class="link" id="check_for_update" >%s.</span>' % (
103 '<span class="link" id="check_for_update" >%s.</span>' % (
104 _('Check for updates')) +
104 _('Check for updates')) +
105 '<br/> <span >%s.</span>' % (update_info_msg)
105 '<br/> <span >%s.</span>' % (update_info_msg)
106 ), ''),
106 ), ''),
107
107
108 # RhodeCode specific
108 # RhodeCode specific
109 (_('RhodeCode Version'), val('rhodecode_app')['text'], state('rhodecode_app')),
109 (_('RhodeCode Version'), val('rhodecode_app')['text'], state('rhodecode_app')),
110 (_('RhodeCode Server IP'), val('server')['server_ip'], state('server')),
110 (_('RhodeCode Server IP'), val('server')['server_ip'], state('server')),
111 (_('RhodeCode Server ID'), val('server')['server_id'], state('server')),
111 (_('RhodeCode Server ID'), val('server')['server_id'], state('server')),
112 (_('RhodeCode Configuration'), val('rhodecode_config')['path'], state('rhodecode_config')),
112 (_('RhodeCode Configuration'), val('rhodecode_config')['path'], state('rhodecode_config')),
113 (_('RhodeCode Certificate'), val('rhodecode_config')['cert_path'], state('rhodecode_config')),
113 (_('RhodeCode Certificate'), val('rhodecode_config')['cert_path'], state('rhodecode_config')),
114 (_('Workers'), val('rhodecode_config')['config']['server:main'].get('workers', '?'), state('rhodecode_config')),
114 (_('Workers'), val('rhodecode_config')['config']['server:main'].get('workers', '?'), state('rhodecode_config')),
115 (_('Worker Type'), val('rhodecode_config')['config']['server:main'].get('worker_class', 'sync'), state('rhodecode_config')),
115 (_('Worker Type'), val('rhodecode_config')['config']['server:main'].get('worker_class', 'sync'), state('rhodecode_config')),
116 ('', '', ''), # spacer
116 ('', '', ''), # spacer
117
117
118 # Database
118 # Database
119 (_('Database'), val('database')['url'], state('database')),
119 (_('Database'), val('database')['url'], state('database')),
120 (_('Database version'), val('database')['version'], state('database')),
120 (_('Database version'), val('database')['version'], state('database')),
121 ('', '', ''), # spacer
121 ('', '', ''), # spacer
122
122
123 # Platform/Python
123 # Platform/Python
124 (_('Platform'), val('platform')['name'], state('platform')),
124 (_('Platform'), val('platform')['name'], state('platform')),
125 (_('Platform UUID'), val('platform')['uuid'], state('platform')),
125 (_('Platform UUID'), val('platform')['uuid'], state('platform')),
126 (_('Python version'), val('python')['version'], state('python')),
126 (_('Python version'), val('python')['version'], state('python')),
127 (_('Python path'), val('python')['executable'], state('python')),
127 (_('Python path'), val('python')['executable'], state('python')),
128 ('', '', ''), # spacer
128 ('', '', ''), # spacer
129
129
130 # Systems stats
130 # Systems stats
131 (_('CPU'), val('cpu')['text'], state('cpu')),
131 (_('CPU'), val('cpu')['text'], state('cpu')),
132 (_('Load'), val('load')['text'], state('load')),
132 (_('Load'), val('load')['text'], state('load')),
133 (_('Memory'), val('memory')['text'], state('memory')),
133 (_('Memory'), val('memory')['text'], state('memory')),
134 (_('Uptime'), val('uptime')['text'], state('uptime')),
134 (_('Uptime'), val('uptime')['text'], state('uptime')),
135 ('', '', ''), # spacer
135 ('', '', ''), # spacer
136
136
137 # Repo storage
137 # Repo storage
138 (_('Storage location'), val('storage')['path'], state('storage')),
138 (_('Storage location'), val('storage')['path'], state('storage')),
139 (_('Storage info'), val('storage')['text'], state('storage')),
139 (_('Storage info'), val('storage')['text'], state('storage')),
140 (_('Storage inodes'), val('storage_inodes')['text'], state('storage_inodes')),
140 (_('Storage inodes'), val('storage_inodes')['text'], state('storage_inodes')),
141
141
142 (_('Gist storage location'), val('storage_gist')['path'], state('storage_gist')),
142 (_('Gist storage location'), val('storage_gist')['path'], state('storage_gist')),
143 (_('Gist storage info'), val('storage_gist')['text'], state('storage_gist')),
143 (_('Gist storage info'), val('storage_gist')['text'], state('storage_gist')),
144
144
145 (_('Archive cache storage location'), val('storage_archive')['path'], state('storage_archive')),
145 (_('Archive cache storage location'), val('storage_archive')['path'], state('storage_archive')),
146 (_('Archive cache info'), val('storage_archive')['text'], state('storage_archive')),
146 (_('Archive cache info'), val('storage_archive')['text'], state('storage_archive')),
147
147
148 (_('Temp storage location'), val('storage_temp')['path'], state('storage_temp')),
148 (_('Temp storage location'), val('storage_temp')['path'], state('storage_temp')),
149 (_('Temp storage info'), val('storage_temp')['text'], state('storage_temp')),
149 (_('Temp storage info'), val('storage_temp')['text'], state('storage_temp')),
150
150
151 (_('Search info'), val('search')['text'], state('search')),
151 (_('Search info'), val('search')['text'], state('search')),
152 (_('Search location'), val('search')['location'], state('search')),
152 (_('Search location'), val('search')['location'], state('search')),
153 ('', '', ''), # spacer
153 ('', '', ''), # spacer
154
154
155 # VCS specific
155 # VCS specific
156 (_('VCS Backends'), val('vcs_backends'), state('vcs_backends')),
156 (_('VCS Backends'), val('vcs_backends'), state('vcs_backends')),
157 (_('VCS Server'), val('vcs_server')['text'], state('vcs_server')),
157 (_('VCS Server'), val('vcs_server')['text'], state('vcs_server')),
158 (_('GIT'), val('git'), state('git')),
158 (_('GIT'), val('git'), state('git')),
159 (_('HG'), val('hg'), state('hg')),
159 (_('HG'), val('hg'), state('hg')),
160 (_('SVN'), val('svn'), state('svn')),
160 (_('SVN'), val('svn'), state('svn')),
161
161
162 ]
162 ]
163
163
164 if snapshot:
164 if snapshot:
165 if c.allowed_to_snapshot:
165 if c.allowed_to_snapshot:
166 c.data_items.pop(0) # remove server info
166 c.data_items.pop(0) # remove server info
167 self.request.override_renderer = 'admin/settings/settings_system_snapshot.mako'
167 self.request.override_renderer = 'admin/settings/settings_system_snapshot.mako'
168 else:
168 else:
169 self.request.session.flash(
169 self.request.session.flash(
170 'You are not allowed to do this', queue='warning')
170 'You are not allowed to do this', queue='warning')
171 return self._get_template_context(c)
171 return self._get_template_context(c)
172
172
173 @LoginRequired()
173 @LoginRequired()
174 @HasPermissionAllDecorator('hg.admin')
174 @HasPermissionAllDecorator('hg.admin')
175 @view_config(
175 @view_config(
176 route_name='admin_settings_system_update', request_method='GET',
176 route_name='admin_settings_system_update', request_method='GET',
177 renderer='rhodecode:templates/admin/settings/settings_system_update.mako')
177 renderer='rhodecode:templates/admin/settings/settings_system_update.mako')
178 def settings_system_info_check_update(self):
178 def settings_system_info_check_update(self):
179 _ = self.request.translate
179 _ = self.request.translate
180 c = self.load_default_context()
180 c = self.load_default_context()
181
181
182 update_url = self.get_update_url()
182 update_url = self.get_update_url()
183
183
184 _err = lambda s: '<div style="color:#ff8888; padding:4px 0px">{}</div>'.format(s)
184 _err = lambda s: '<div style="color:#ff8888; padding:4px 0px">{}</div>'.format(s)
185 try:
185 try:
186 data = self.get_update_data(update_url)
186 data = self.get_update_data(update_url)
187 except urllib2.URLError as e:
187 except urllib2.URLError as e:
188 log.exception("Exception contacting upgrade server")
188 log.exception("Exception contacting upgrade server")
189 self.request.override_renderer = 'string'
189 self.request.override_renderer = 'string'
190 return _err('Failed to contact upgrade server: %r' % e)
190 return _err('Failed to contact upgrade server: %r' % e)
191 except ValueError as e:
191 except ValueError as e:
192 log.exception("Bad data sent from update server")
192 log.exception("Bad data sent from update server")
193 self.request.override_renderer = 'string'
193 self.request.override_renderer = 'string'
194 return _err('Bad data sent from update server')
194 return _err('Bad data sent from update server')
195
195
196 latest = data['versions'][0]
196 latest = data['versions'][0]
197
197
198 c.update_url = update_url
198 c.update_url = update_url
199 c.latest_data = latest
199 c.latest_data = latest
200 c.latest_ver = latest['version']
200 c.latest_ver = latest['version']
201 c.cur_ver = rhodecode.__version__
201 c.cur_ver = rhodecode.__version__
202 c.should_upgrade = False
202 c.should_upgrade = False
203
203
204 if (packaging.version.Version(c.latest_ver) >
204 if (packaging.version.Version(c.latest_ver) >
205 packaging.version.Version(c.cur_ver)):
205 packaging.version.Version(c.cur_ver)):
206 c.should_upgrade = True
206 c.should_upgrade = True
207 c.important_notices = latest['general']
207 c.important_notices = latest['general']
208
208
209 return self._get_template_context(c)
209 return self._get_template_context(c)
@@ -1,248 +1,248 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22
22
23 import formencode
23 import formencode
24 import formencode.htmlfill
24 import formencode.htmlfill
25
25
26 from pyramid.httpexceptions import HTTPFound
26 from pyramid.httpexceptions import HTTPFound
27 from pyramid.view import view_config
27 from pyramid.view import view_config
28 from pyramid.response import Response
28 from pyramid.response import Response
29 from pyramid.renderers import render
29 from pyramid.renderers import render
30
30
31 from rhodecode.apps._base import BaseAppView, DataGridAppView
31 from rhodecode.apps._base import BaseAppView, DataGridAppView
32 from rhodecode.lib.auth import (
32 from rhodecode.lib.auth import (
33 LoginRequired, NotAnonymous, CSRFRequired, HasPermissionAnyDecorator)
33 LoginRequired, NotAnonymous, CSRFRequired, HasPermissionAnyDecorator)
34 from rhodecode.lib import helpers as h, audit_logger
34 from rhodecode.lib import helpers as h, audit_logger
35 from rhodecode.lib.utils2 import safe_unicode
35 from rhodecode.lib.utils2 import safe_unicode
36
36
37 from rhodecode.model.forms import UserGroupForm
37 from rhodecode.model.forms import UserGroupForm
38 from rhodecode.model.permission import PermissionModel
38 from rhodecode.model.permission import PermissionModel
39 from rhodecode.model.scm import UserGroupList
39 from rhodecode.model.scm import UserGroupList
40 from rhodecode.model.db import (
40 from rhodecode.model.db import (
41 or_, count, User, UserGroup, UserGroupMember)
41 or_, count, User, UserGroup, UserGroupMember)
42 from rhodecode.model.meta import Session
42 from rhodecode.model.meta import Session
43 from rhodecode.model.user_group import UserGroupModel
43 from rhodecode.model.user_group import UserGroupModel
44
44
45 log = logging.getLogger(__name__)
45 log = logging.getLogger(__name__)
46
46
47
47
48 class AdminUserGroupsView(BaseAppView, DataGridAppView):
48 class AdminUserGroupsView(BaseAppView, DataGridAppView):
49
49
50 def load_default_context(self):
50 def load_default_context(self):
51 c = self._get_local_tmpl_context()
51 c = self._get_local_tmpl_context()
52
52
53 PermissionModel().set_global_permission_choices(
53 PermissionModel().set_global_permission_choices(
54 c, gettext_translator=self.request.translate)
54 c, gettext_translator=self.request.translate)
55
55
56 self._register_global_c(c)
56
57 return c
57 return c
58
58
59 # permission check in data loading of
59 # permission check in data loading of
60 # `user_groups_list_data` via UserGroupList
60 # `user_groups_list_data` via UserGroupList
61 @LoginRequired()
61 @LoginRequired()
62 @NotAnonymous()
62 @NotAnonymous()
63 @view_config(
63 @view_config(
64 route_name='user_groups', request_method='GET',
64 route_name='user_groups', request_method='GET',
65 renderer='rhodecode:templates/admin/user_groups/user_groups.mako')
65 renderer='rhodecode:templates/admin/user_groups/user_groups.mako')
66 def user_groups_list(self):
66 def user_groups_list(self):
67 c = self.load_default_context()
67 c = self.load_default_context()
68 return self._get_template_context(c)
68 return self._get_template_context(c)
69
69
70 # permission check inside
70 # permission check inside
71 @LoginRequired()
71 @LoginRequired()
72 @NotAnonymous()
72 @NotAnonymous()
73 @view_config(
73 @view_config(
74 route_name='user_groups_data', request_method='GET',
74 route_name='user_groups_data', request_method='GET',
75 renderer='json_ext', xhr=True)
75 renderer='json_ext', xhr=True)
76 def user_groups_list_data(self):
76 def user_groups_list_data(self):
77 self.load_default_context()
77 self.load_default_context()
78 column_map = {
78 column_map = {
79 'active': 'users_group_active',
79 'active': 'users_group_active',
80 'description': 'user_group_description',
80 'description': 'user_group_description',
81 'members': 'members_total',
81 'members': 'members_total',
82 'owner': 'user_username',
82 'owner': 'user_username',
83 'sync': 'group_data'
83 'sync': 'group_data'
84 }
84 }
85 draw, start, limit = self._extract_chunk(self.request)
85 draw, start, limit = self._extract_chunk(self.request)
86 search_q, order_by, order_dir = self._extract_ordering(
86 search_q, order_by, order_dir = self._extract_ordering(
87 self.request, column_map=column_map)
87 self.request, column_map=column_map)
88
88
89 _render = self.request.get_partial_renderer(
89 _render = self.request.get_partial_renderer(
90 'rhodecode:templates/data_table/_dt_elements.mako')
90 'rhodecode:templates/data_table/_dt_elements.mako')
91
91
92 def user_group_name(user_group_id, user_group_name):
92 def user_group_name(user_group_id, user_group_name):
93 return _render("user_group_name", user_group_id, user_group_name)
93 return _render("user_group_name", user_group_id, user_group_name)
94
94
95 def user_group_actions(user_group_id, user_group_name):
95 def user_group_actions(user_group_id, user_group_name):
96 return _render("user_group_actions", user_group_id, user_group_name)
96 return _render("user_group_actions", user_group_id, user_group_name)
97
97
98 def user_profile(username):
98 def user_profile(username):
99 return _render('user_profile', username)
99 return _render('user_profile', username)
100
100
101 auth_user_group_list = UserGroupList(
101 auth_user_group_list = UserGroupList(
102 UserGroup.query().all(), perm_set=['usergroup.admin'])
102 UserGroup.query().all(), perm_set=['usergroup.admin'])
103
103
104 allowed_ids = [-1]
104 allowed_ids = [-1]
105 for user_group in auth_user_group_list:
105 for user_group in auth_user_group_list:
106 allowed_ids.append(user_group.users_group_id)
106 allowed_ids.append(user_group.users_group_id)
107
107
108 user_groups_data_total_count = UserGroup.query()\
108 user_groups_data_total_count = UserGroup.query()\
109 .filter(UserGroup.users_group_id.in_(allowed_ids))\
109 .filter(UserGroup.users_group_id.in_(allowed_ids))\
110 .count()
110 .count()
111
111
112 member_count = count(UserGroupMember.user_id)
112 member_count = count(UserGroupMember.user_id)
113 base_q = Session.query(
113 base_q = Session.query(
114 UserGroup.users_group_name,
114 UserGroup.users_group_name,
115 UserGroup.user_group_description,
115 UserGroup.user_group_description,
116 UserGroup.users_group_active,
116 UserGroup.users_group_active,
117 UserGroup.users_group_id,
117 UserGroup.users_group_id,
118 UserGroup.group_data,
118 UserGroup.group_data,
119 User,
119 User,
120 member_count.label('member_count')
120 member_count.label('member_count')
121 ) \
121 ) \
122 .filter(UserGroup.users_group_id.in_(allowed_ids)) \
122 .filter(UserGroup.users_group_id.in_(allowed_ids)) \
123 .outerjoin(UserGroupMember) \
123 .outerjoin(UserGroupMember) \
124 .join(User, User.user_id == UserGroup.user_id) \
124 .join(User, User.user_id == UserGroup.user_id) \
125 .group_by(UserGroup, User)
125 .group_by(UserGroup, User)
126
126
127 if search_q:
127 if search_q:
128 like_expression = u'%{}%'.format(safe_unicode(search_q))
128 like_expression = u'%{}%'.format(safe_unicode(search_q))
129 base_q = base_q.filter(or_(
129 base_q = base_q.filter(or_(
130 UserGroup.users_group_name.ilike(like_expression),
130 UserGroup.users_group_name.ilike(like_expression),
131 ))
131 ))
132
132
133 user_groups_data_total_filtered_count = base_q.count()
133 user_groups_data_total_filtered_count = base_q.count()
134
134
135 if order_by == 'members_total':
135 if order_by == 'members_total':
136 sort_col = member_count
136 sort_col = member_count
137 elif order_by == 'user_username':
137 elif order_by == 'user_username':
138 sort_col = User.username
138 sort_col = User.username
139 else:
139 else:
140 sort_col = getattr(UserGroup, order_by, None)
140 sort_col = getattr(UserGroup, order_by, None)
141
141
142 if isinstance(sort_col, count) or sort_col:
142 if isinstance(sort_col, count) or sort_col:
143 if order_dir == 'asc':
143 if order_dir == 'asc':
144 sort_col = sort_col.asc()
144 sort_col = sort_col.asc()
145 else:
145 else:
146 sort_col = sort_col.desc()
146 sort_col = sort_col.desc()
147
147
148 base_q = base_q.order_by(sort_col)
148 base_q = base_q.order_by(sort_col)
149 base_q = base_q.offset(start).limit(limit)
149 base_q = base_q.offset(start).limit(limit)
150
150
151 # authenticated access to user groups
151 # authenticated access to user groups
152 auth_user_group_list = base_q.all()
152 auth_user_group_list = base_q.all()
153
153
154 user_groups_data = []
154 user_groups_data = []
155 for user_gr in auth_user_group_list:
155 for user_gr in auth_user_group_list:
156 user_groups_data.append({
156 user_groups_data.append({
157 "users_group_name": user_group_name(
157 "users_group_name": user_group_name(
158 user_gr.users_group_id, h.escape(user_gr.users_group_name)),
158 user_gr.users_group_id, h.escape(user_gr.users_group_name)),
159 "name_raw": h.escape(user_gr.users_group_name),
159 "name_raw": h.escape(user_gr.users_group_name),
160 "description": h.escape(user_gr.user_group_description),
160 "description": h.escape(user_gr.user_group_description),
161 "members": user_gr.member_count,
161 "members": user_gr.member_count,
162 # NOTE(marcink): because of advanced query we
162 # NOTE(marcink): because of advanced query we
163 # need to load it like that
163 # need to load it like that
164 "sync": UserGroup._load_group_data(
164 "sync": UserGroup._load_group_data(
165 user_gr.group_data).get('extern_type'),
165 user_gr.group_data).get('extern_type'),
166 "active": h.bool2icon(user_gr.users_group_active),
166 "active": h.bool2icon(user_gr.users_group_active),
167 "owner": user_profile(user_gr.User.username),
167 "owner": user_profile(user_gr.User.username),
168 "action": user_group_actions(
168 "action": user_group_actions(
169 user_gr.users_group_id, user_gr.users_group_name)
169 user_gr.users_group_id, user_gr.users_group_name)
170 })
170 })
171
171
172 data = ({
172 data = ({
173 'draw': draw,
173 'draw': draw,
174 'data': user_groups_data,
174 'data': user_groups_data,
175 'recordsTotal': user_groups_data_total_count,
175 'recordsTotal': user_groups_data_total_count,
176 'recordsFiltered': user_groups_data_total_filtered_count,
176 'recordsFiltered': user_groups_data_total_filtered_count,
177 })
177 })
178
178
179 return data
179 return data
180
180
181 @LoginRequired()
181 @LoginRequired()
182 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
182 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
183 @view_config(
183 @view_config(
184 route_name='user_groups_new', request_method='GET',
184 route_name='user_groups_new', request_method='GET',
185 renderer='rhodecode:templates/admin/user_groups/user_group_add.mako')
185 renderer='rhodecode:templates/admin/user_groups/user_group_add.mako')
186 def user_groups_new(self):
186 def user_groups_new(self):
187 c = self.load_default_context()
187 c = self.load_default_context()
188 return self._get_template_context(c)
188 return self._get_template_context(c)
189
189
190 @LoginRequired()
190 @LoginRequired()
191 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
191 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
192 @CSRFRequired()
192 @CSRFRequired()
193 @view_config(
193 @view_config(
194 route_name='user_groups_create', request_method='POST',
194 route_name='user_groups_create', request_method='POST',
195 renderer='rhodecode:templates/admin/user_groups/user_group_add.mako')
195 renderer='rhodecode:templates/admin/user_groups/user_group_add.mako')
196 def user_groups_create(self):
196 def user_groups_create(self):
197 _ = self.request.translate
197 _ = self.request.translate
198 c = self.load_default_context()
198 c = self.load_default_context()
199 users_group_form = UserGroupForm()()
199 users_group_form = UserGroupForm(self.request.translate)()
200
200
201 user_group_name = self.request.POST.get('users_group_name')
201 user_group_name = self.request.POST.get('users_group_name')
202 try:
202 try:
203 form_result = users_group_form.to_python(dict(self.request.POST))
203 form_result = users_group_form.to_python(dict(self.request.POST))
204 user_group = UserGroupModel().create(
204 user_group = UserGroupModel().create(
205 name=form_result['users_group_name'],
205 name=form_result['users_group_name'],
206 description=form_result['user_group_description'],
206 description=form_result['user_group_description'],
207 owner=self._rhodecode_user.user_id,
207 owner=self._rhodecode_user.user_id,
208 active=form_result['users_group_active'])
208 active=form_result['users_group_active'])
209 Session().flush()
209 Session().flush()
210 creation_data = user_group.get_api_data()
210 creation_data = user_group.get_api_data()
211 user_group_name = form_result['users_group_name']
211 user_group_name = form_result['users_group_name']
212
212
213 audit_logger.store_web(
213 audit_logger.store_web(
214 'user_group.create', action_data={'data': creation_data},
214 'user_group.create', action_data={'data': creation_data},
215 user=self._rhodecode_user)
215 user=self._rhodecode_user)
216
216
217 user_group_link = h.link_to(
217 user_group_link = h.link_to(
218 h.escape(user_group_name),
218 h.escape(user_group_name),
219 h.route_path(
219 h.route_path(
220 'edit_user_group', user_group_id=user_group.users_group_id))
220 'edit_user_group', user_group_id=user_group.users_group_id))
221 h.flash(h.literal(_('Created user group %(user_group_link)s')
221 h.flash(h.literal(_('Created user group %(user_group_link)s')
222 % {'user_group_link': user_group_link}),
222 % {'user_group_link': user_group_link}),
223 category='success')
223 category='success')
224 Session().commit()
224 Session().commit()
225 user_group_id = user_group.users_group_id
225 user_group_id = user_group.users_group_id
226 except formencode.Invalid as errors:
226 except formencode.Invalid as errors:
227
227
228 data = render(
228 data = render(
229 'rhodecode:templates/admin/user_groups/user_group_add.mako',
229 'rhodecode:templates/admin/user_groups/user_group_add.mako',
230 self._get_template_context(c), self.request)
230 self._get_template_context(c), self.request)
231 html = formencode.htmlfill.render(
231 html = formencode.htmlfill.render(
232 data,
232 data,
233 defaults=errors.value,
233 defaults=errors.value,
234 errors=errors.error_dict or {},
234 errors=errors.error_dict or {},
235 prefix_error=False,
235 prefix_error=False,
236 encoding="UTF-8",
236 encoding="UTF-8",
237 force_defaults=False
237 force_defaults=False
238 )
238 )
239 return Response(html)
239 return Response(html)
240
240
241 except Exception:
241 except Exception:
242 log.exception("Exception creating user group")
242 log.exception("Exception creating user group")
243 h.flash(_('Error occurred during creation of user group %s') \
243 h.flash(_('Error occurred during creation of user group %s') \
244 % user_group_name, category='error')
244 % user_group_name, category='error')
245 raise HTTPFound(h.route_path('user_groups_new'))
245 raise HTTPFound(h.route_path('user_groups_new'))
246
246
247 raise HTTPFound(
247 raise HTTPFound(
248 h.route_path('edit_user_group', user_group_id=user_group_id))
248 h.route_path('edit_user_group', user_group_id=user_group_id))
@@ -1,1179 +1,1190 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 import datetime
22 import datetime
23 import formencode
23 import formencode
24 import formencode.htmlfill
24 import formencode.htmlfill
25
25
26 from pyramid.httpexceptions import HTTPFound
26 from pyramid.httpexceptions import HTTPFound
27 from pyramid.view import view_config
27 from pyramid.view import view_config
28 from pyramid.renderers import render
28 from pyramid.renderers import render
29 from pyramid.response import Response
29 from pyramid.response import Response
30
30
31 from rhodecode.apps._base import BaseAppView, DataGridAppView, UserAppView
31 from rhodecode.apps._base import BaseAppView, DataGridAppView, UserAppView
32 from rhodecode.apps.ssh_support import SshKeyFileChangeEvent
32 from rhodecode.apps.ssh_support import SshKeyFileChangeEvent
33 from rhodecode.authentication.plugins import auth_rhodecode
33 from rhodecode.authentication.plugins import auth_rhodecode
34 from rhodecode.events import trigger
34 from rhodecode.events import trigger
35
35
36 from rhodecode.lib import audit_logger
36 from rhodecode.lib import audit_logger
37 from rhodecode.lib.exceptions import (
37 from rhodecode.lib.exceptions import (
38 UserCreationError, UserOwnsReposException, UserOwnsRepoGroupsException,
38 UserCreationError, UserOwnsReposException, UserOwnsRepoGroupsException,
39 UserOwnsUserGroupsException, DefaultUserException)
39 UserOwnsUserGroupsException, DefaultUserException)
40 from rhodecode.lib.ext_json import json
40 from rhodecode.lib.ext_json import json
41 from rhodecode.lib.auth import (
41 from rhodecode.lib.auth import (
42 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
42 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
43 from rhodecode.lib import helpers as h
43 from rhodecode.lib import helpers as h
44 from rhodecode.lib.utils2 import safe_int, safe_unicode, AttributeDict
44 from rhodecode.lib.utils2 import safe_int, safe_unicode, AttributeDict
45 from rhodecode.model.auth_token import AuthTokenModel
45 from rhodecode.model.auth_token import AuthTokenModel
46 from rhodecode.model.forms import (
46 from rhodecode.model.forms import (
47 UserForm, UserIndividualPermissionsForm, UserPermissionsForm)
47 UserForm, UserIndividualPermissionsForm, UserPermissionsForm,
48 UserExtraEmailForm, UserExtraIpForm)
48 from rhodecode.model.permission import PermissionModel
49 from rhodecode.model.permission import PermissionModel
49 from rhodecode.model.repo_group import RepoGroupModel
50 from rhodecode.model.repo_group import RepoGroupModel
50 from rhodecode.model.ssh_key import SshKeyModel
51 from rhodecode.model.ssh_key import SshKeyModel
51 from rhodecode.model.user import UserModel
52 from rhodecode.model.user import UserModel
52 from rhodecode.model.user_group import UserGroupModel
53 from rhodecode.model.user_group import UserGroupModel
53 from rhodecode.model.db import (
54 from rhodecode.model.db import (
54 or_, coalesce,IntegrityError, User, UserGroup, UserIpMap, UserEmailMap,
55 or_, coalesce,IntegrityError, User, UserGroup, UserIpMap, UserEmailMap,
55 UserApiKeys, UserSshKeys, RepoGroup)
56 UserApiKeys, UserSshKeys, RepoGroup)
56 from rhodecode.model.meta import Session
57 from rhodecode.model.meta import Session
57
58
58 log = logging.getLogger(__name__)
59 log = logging.getLogger(__name__)
59
60
60
61
61 class AdminUsersView(BaseAppView, DataGridAppView):
62 class AdminUsersView(BaseAppView, DataGridAppView):
62
63
63 def load_default_context(self):
64 def load_default_context(self):
64 c = self._get_local_tmpl_context()
65 c = self._get_local_tmpl_context()
65 self._register_global_c(c)
66 return c
66 return c
67
67
68 @LoginRequired()
68 @LoginRequired()
69 @HasPermissionAllDecorator('hg.admin')
69 @HasPermissionAllDecorator('hg.admin')
70 @view_config(
70 @view_config(
71 route_name='users', request_method='GET',
71 route_name='users', request_method='GET',
72 renderer='rhodecode:templates/admin/users/users.mako')
72 renderer='rhodecode:templates/admin/users/users.mako')
73 def users_list(self):
73 def users_list(self):
74 c = self.load_default_context()
74 c = self.load_default_context()
75 return self._get_template_context(c)
75 return self._get_template_context(c)
76
76
77 @LoginRequired()
77 @LoginRequired()
78 @HasPermissionAllDecorator('hg.admin')
78 @HasPermissionAllDecorator('hg.admin')
79 @view_config(
79 @view_config(
80 # renderer defined below
80 # renderer defined below
81 route_name='users_data', request_method='GET',
81 route_name='users_data', request_method='GET',
82 renderer='json_ext', xhr=True)
82 renderer='json_ext', xhr=True)
83 def users_list_data(self):
83 def users_list_data(self):
84 self.load_default_context()
84 self.load_default_context()
85 column_map = {
85 column_map = {
86 'first_name': 'name',
86 'first_name': 'name',
87 'last_name': 'lastname',
87 'last_name': 'lastname',
88 }
88 }
89 draw, start, limit = self._extract_chunk(self.request)
89 draw, start, limit = self._extract_chunk(self.request)
90 search_q, order_by, order_dir = self._extract_ordering(
90 search_q, order_by, order_dir = self._extract_ordering(
91 self.request, column_map=column_map)
91 self.request, column_map=column_map)
92
92
93 _render = self.request.get_partial_renderer(
93 _render = self.request.get_partial_renderer(
94 'rhodecode:templates/data_table/_dt_elements.mako')
94 'rhodecode:templates/data_table/_dt_elements.mako')
95
95
96 def user_actions(user_id, username):
96 def user_actions(user_id, username):
97 return _render("user_actions", user_id, username)
97 return _render("user_actions", user_id, username)
98
98
99 users_data_total_count = User.query()\
99 users_data_total_count = User.query()\
100 .filter(User.username != User.DEFAULT_USER) \
100 .filter(User.username != User.DEFAULT_USER) \
101 .count()
101 .count()
102
102
103 # json generate
103 # json generate
104 base_q = User.query().filter(User.username != User.DEFAULT_USER)
104 base_q = User.query().filter(User.username != User.DEFAULT_USER)
105
105
106 if search_q:
106 if search_q:
107 like_expression = u'%{}%'.format(safe_unicode(search_q))
107 like_expression = u'%{}%'.format(safe_unicode(search_q))
108 base_q = base_q.filter(or_(
108 base_q = base_q.filter(or_(
109 User.username.ilike(like_expression),
109 User.username.ilike(like_expression),
110 User._email.ilike(like_expression),
110 User._email.ilike(like_expression),
111 User.name.ilike(like_expression),
111 User.name.ilike(like_expression),
112 User.lastname.ilike(like_expression),
112 User.lastname.ilike(like_expression),
113 ))
113 ))
114
114
115 users_data_total_filtered_count = base_q.count()
115 users_data_total_filtered_count = base_q.count()
116
116
117 sort_col = getattr(User, order_by, None)
117 sort_col = getattr(User, order_by, None)
118 if sort_col:
118 if sort_col:
119 if order_dir == 'asc':
119 if order_dir == 'asc':
120 # handle null values properly to order by NULL last
120 # handle null values properly to order by NULL last
121 if order_by in ['last_activity']:
121 if order_by in ['last_activity']:
122 sort_col = coalesce(sort_col, datetime.date.max)
122 sort_col = coalesce(sort_col, datetime.date.max)
123 sort_col = sort_col.asc()
123 sort_col = sort_col.asc()
124 else:
124 else:
125 # handle null values properly to order by NULL last
125 # handle null values properly to order by NULL last
126 if order_by in ['last_activity']:
126 if order_by in ['last_activity']:
127 sort_col = coalesce(sort_col, datetime.date.min)
127 sort_col = coalesce(sort_col, datetime.date.min)
128 sort_col = sort_col.desc()
128 sort_col = sort_col.desc()
129
129
130 base_q = base_q.order_by(sort_col)
130 base_q = base_q.order_by(sort_col)
131 base_q = base_q.offset(start).limit(limit)
131 base_q = base_q.offset(start).limit(limit)
132
132
133 users_list = base_q.all()
133 users_list = base_q.all()
134
134
135 users_data = []
135 users_data = []
136 for user in users_list:
136 for user in users_list:
137 users_data.append({
137 users_data.append({
138 "username": h.gravatar_with_user(self.request, user.username),
138 "username": h.gravatar_with_user(self.request, user.username),
139 "email": user.email,
139 "email": user.email,
140 "first_name": user.first_name,
140 "first_name": user.first_name,
141 "last_name": user.last_name,
141 "last_name": user.last_name,
142 "last_login": h.format_date(user.last_login),
142 "last_login": h.format_date(user.last_login),
143 "last_activity": h.format_date(user.last_activity),
143 "last_activity": h.format_date(user.last_activity),
144 "active": h.bool2icon(user.active),
144 "active": h.bool2icon(user.active),
145 "active_raw": user.active,
145 "active_raw": user.active,
146 "admin": h.bool2icon(user.admin),
146 "admin": h.bool2icon(user.admin),
147 "extern_type": user.extern_type,
147 "extern_type": user.extern_type,
148 "extern_name": user.extern_name,
148 "extern_name": user.extern_name,
149 "action": user_actions(user.user_id, user.username),
149 "action": user_actions(user.user_id, user.username),
150 })
150 })
151
151
152 data = ({
152 data = ({
153 'draw': draw,
153 'draw': draw,
154 'data': users_data,
154 'data': users_data,
155 'recordsTotal': users_data_total_count,
155 'recordsTotal': users_data_total_count,
156 'recordsFiltered': users_data_total_filtered_count,
156 'recordsFiltered': users_data_total_filtered_count,
157 })
157 })
158
158
159 return data
159 return data
160
160
161 def _set_personal_repo_group_template_vars(self, c_obj):
161 def _set_personal_repo_group_template_vars(self, c_obj):
162 DummyUser = AttributeDict({
162 DummyUser = AttributeDict({
163 'username': '${username}',
163 'username': '${username}',
164 'user_id': '${user_id}',
164 'user_id': '${user_id}',
165 })
165 })
166 c_obj.default_create_repo_group = RepoGroupModel() \
166 c_obj.default_create_repo_group = RepoGroupModel() \
167 .get_default_create_personal_repo_group()
167 .get_default_create_personal_repo_group()
168 c_obj.personal_repo_group_name = RepoGroupModel() \
168 c_obj.personal_repo_group_name = RepoGroupModel() \
169 .get_personal_group_name(DummyUser)
169 .get_personal_group_name(DummyUser)
170
170
171 @LoginRequired()
171 @LoginRequired()
172 @HasPermissionAllDecorator('hg.admin')
172 @HasPermissionAllDecorator('hg.admin')
173 @view_config(
173 @view_config(
174 route_name='users_new', request_method='GET',
174 route_name='users_new', request_method='GET',
175 renderer='rhodecode:templates/admin/users/user_add.mako')
175 renderer='rhodecode:templates/admin/users/user_add.mako')
176 def users_new(self):
176 def users_new(self):
177 _ = self.request.translate
177 _ = self.request.translate
178 c = self.load_default_context()
178 c = self.load_default_context()
179 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
179 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
180 self._set_personal_repo_group_template_vars(c)
180 self._set_personal_repo_group_template_vars(c)
181 return self._get_template_context(c)
181 return self._get_template_context(c)
182
182
183 @LoginRequired()
183 @LoginRequired()
184 @HasPermissionAllDecorator('hg.admin')
184 @HasPermissionAllDecorator('hg.admin')
185 @CSRFRequired()
185 @CSRFRequired()
186 @view_config(
186 @view_config(
187 route_name='users_create', request_method='POST',
187 route_name='users_create', request_method='POST',
188 renderer='rhodecode:templates/admin/users/user_add.mako')
188 renderer='rhodecode:templates/admin/users/user_add.mako')
189 def users_create(self):
189 def users_create(self):
190 _ = self.request.translate
190 _ = self.request.translate
191 c = self.load_default_context()
191 c = self.load_default_context()
192 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
192 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
193 user_model = UserModel()
193 user_model = UserModel()
194 user_form = UserForm()()
194 user_form = UserForm(self.request.translate)()
195 try:
195 try:
196 form_result = user_form.to_python(dict(self.request.POST))
196 form_result = user_form.to_python(dict(self.request.POST))
197 user = user_model.create(form_result)
197 user = user_model.create(form_result)
198 Session().flush()
198 Session().flush()
199 creation_data = user.get_api_data()
199 creation_data = user.get_api_data()
200 username = form_result['username']
200 username = form_result['username']
201
201
202 audit_logger.store_web(
202 audit_logger.store_web(
203 'user.create', action_data={'data': creation_data},
203 'user.create', action_data={'data': creation_data},
204 user=c.rhodecode_user)
204 user=c.rhodecode_user)
205
205
206 user_link = h.link_to(
206 user_link = h.link_to(
207 h.escape(username),
207 h.escape(username),
208 h.route_path('user_edit', user_id=user.user_id))
208 h.route_path('user_edit', user_id=user.user_id))
209 h.flash(h.literal(_('Created user %(user_link)s')
209 h.flash(h.literal(_('Created user %(user_link)s')
210 % {'user_link': user_link}), category='success')
210 % {'user_link': user_link}), category='success')
211 Session().commit()
211 Session().commit()
212 except formencode.Invalid as errors:
212 except formencode.Invalid as errors:
213 self._set_personal_repo_group_template_vars(c)
213 self._set_personal_repo_group_template_vars(c)
214 data = render(
214 data = render(
215 'rhodecode:templates/admin/users/user_add.mako',
215 'rhodecode:templates/admin/users/user_add.mako',
216 self._get_template_context(c), self.request)
216 self._get_template_context(c), self.request)
217 html = formencode.htmlfill.render(
217 html = formencode.htmlfill.render(
218 data,
218 data,
219 defaults=errors.value,
219 defaults=errors.value,
220 errors=errors.error_dict or {},
220 errors=errors.error_dict or {},
221 prefix_error=False,
221 prefix_error=False,
222 encoding="UTF-8",
222 encoding="UTF-8",
223 force_defaults=False
223 force_defaults=False
224 )
224 )
225 return Response(html)
225 return Response(html)
226 except UserCreationError as e:
226 except UserCreationError as e:
227 h.flash(e, 'error')
227 h.flash(e, 'error')
228 except Exception:
228 except Exception:
229 log.exception("Exception creation of user")
229 log.exception("Exception creation of user")
230 h.flash(_('Error occurred during creation of user %s')
230 h.flash(_('Error occurred during creation of user %s')
231 % self.request.POST.get('username'), category='error')
231 % self.request.POST.get('username'), category='error')
232 raise HTTPFound(h.route_path('users'))
232 raise HTTPFound(h.route_path('users'))
233
233
234
234
235 class UsersView(UserAppView):
235 class UsersView(UserAppView):
236 ALLOW_SCOPED_TOKENS = False
236 ALLOW_SCOPED_TOKENS = False
237 """
237 """
238 This view has alternative version inside EE, if modified please take a look
238 This view has alternative version inside EE, if modified please take a look
239 in there as well.
239 in there as well.
240 """
240 """
241
241
242 def load_default_context(self):
242 def load_default_context(self):
243 c = self._get_local_tmpl_context()
243 c = self._get_local_tmpl_context()
244 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
244 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
245 c.allowed_languages = [
245 c.allowed_languages = [
246 ('en', 'English (en)'),
246 ('en', 'English (en)'),
247 ('de', 'German (de)'),
247 ('de', 'German (de)'),
248 ('fr', 'French (fr)'),
248 ('fr', 'French (fr)'),
249 ('it', 'Italian (it)'),
249 ('it', 'Italian (it)'),
250 ('ja', 'Japanese (ja)'),
250 ('ja', 'Japanese (ja)'),
251 ('pl', 'Polish (pl)'),
251 ('pl', 'Polish (pl)'),
252 ('pt', 'Portuguese (pt)'),
252 ('pt', 'Portuguese (pt)'),
253 ('ru', 'Russian (ru)'),
253 ('ru', 'Russian (ru)'),
254 ('zh', 'Chinese (zh)'),
254 ('zh', 'Chinese (zh)'),
255 ]
255 ]
256 req = self.request
256 req = self.request
257
257
258 c.available_permissions = req.registry.settings['available_permissions']
258 c.available_permissions = req.registry.settings['available_permissions']
259 PermissionModel().set_global_permission_choices(
259 PermissionModel().set_global_permission_choices(
260 c, gettext_translator=req.translate)
260 c, gettext_translator=req.translate)
261
261
262 self._register_global_c(c)
262
263 return c
263 return c
264
264
265 @LoginRequired()
265 @LoginRequired()
266 @HasPermissionAllDecorator('hg.admin')
266 @HasPermissionAllDecorator('hg.admin')
267 @CSRFRequired()
267 @CSRFRequired()
268 @view_config(
268 @view_config(
269 route_name='user_update', request_method='POST',
269 route_name='user_update', request_method='POST',
270 renderer='rhodecode:templates/admin/users/user_edit.mako')
270 renderer='rhodecode:templates/admin/users/user_edit.mako')
271 def user_update(self):
271 def user_update(self):
272 _ = self.request.translate
272 _ = self.request.translate
273 c = self.load_default_context()
273 c = self.load_default_context()
274
274
275 user_id = self.db_user_id
275 user_id = self.db_user_id
276 c.user = self.db_user
276 c.user = self.db_user
277
277
278 c.active = 'profile'
278 c.active = 'profile'
279 c.extern_type = c.user.extern_type
279 c.extern_type = c.user.extern_type
280 c.extern_name = c.user.extern_name
280 c.extern_name = c.user.extern_name
281 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
281 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
282 available_languages = [x[0] for x in c.allowed_languages]
282 available_languages = [x[0] for x in c.allowed_languages]
283 _form = UserForm(edit=True, available_languages=available_languages,
283 _form = UserForm(self.request.translate, edit=True,
284 available_languages=available_languages,
284 old_data={'user_id': user_id,
285 old_data={'user_id': user_id,
285 'email': c.user.email})()
286 'email': c.user.email})()
286 form_result = {}
287 form_result = {}
287 old_values = c.user.get_api_data()
288 old_values = c.user.get_api_data()
288 try:
289 try:
289 form_result = _form.to_python(dict(self.request.POST))
290 form_result = _form.to_python(dict(self.request.POST))
290 skip_attrs = ['extern_type', 'extern_name']
291 skip_attrs = ['extern_type', 'extern_name']
291 # TODO: plugin should define if username can be updated
292 # TODO: plugin should define if username can be updated
292 if c.extern_type != "rhodecode":
293 if c.extern_type != "rhodecode":
293 # forbid updating username for external accounts
294 # forbid updating username for external accounts
294 skip_attrs.append('username')
295 skip_attrs.append('username')
295
296
296 UserModel().update_user(
297 UserModel().update_user(
297 user_id, skip_attrs=skip_attrs, **form_result)
298 user_id, skip_attrs=skip_attrs, **form_result)
298
299
299 audit_logger.store_web(
300 audit_logger.store_web(
300 'user.edit', action_data={'old_data': old_values},
301 'user.edit', action_data={'old_data': old_values},
301 user=c.rhodecode_user)
302 user=c.rhodecode_user)
302
303
303 Session().commit()
304 Session().commit()
304 h.flash(_('User updated successfully'), category='success')
305 h.flash(_('User updated successfully'), category='success')
305 except formencode.Invalid as errors:
306 except formencode.Invalid as errors:
306 data = render(
307 data = render(
307 'rhodecode:templates/admin/users/user_edit.mako',
308 'rhodecode:templates/admin/users/user_edit.mako',
308 self._get_template_context(c), self.request)
309 self._get_template_context(c), self.request)
309 html = formencode.htmlfill.render(
310 html = formencode.htmlfill.render(
310 data,
311 data,
311 defaults=errors.value,
312 defaults=errors.value,
312 errors=errors.error_dict or {},
313 errors=errors.error_dict or {},
313 prefix_error=False,
314 prefix_error=False,
314 encoding="UTF-8",
315 encoding="UTF-8",
315 force_defaults=False
316 force_defaults=False
316 )
317 )
317 return Response(html)
318 return Response(html)
318 except UserCreationError as e:
319 except UserCreationError as e:
319 h.flash(e, 'error')
320 h.flash(e, 'error')
320 except Exception:
321 except Exception:
321 log.exception("Exception updating user")
322 log.exception("Exception updating user")
322 h.flash(_('Error occurred during update of user %s')
323 h.flash(_('Error occurred during update of user %s')
323 % form_result.get('username'), category='error')
324 % form_result.get('username'), category='error')
324 raise HTTPFound(h.route_path('user_edit', user_id=user_id))
325 raise HTTPFound(h.route_path('user_edit', user_id=user_id))
325
326
326 @LoginRequired()
327 @LoginRequired()
327 @HasPermissionAllDecorator('hg.admin')
328 @HasPermissionAllDecorator('hg.admin')
328 @CSRFRequired()
329 @CSRFRequired()
329 @view_config(
330 @view_config(
330 route_name='user_delete', request_method='POST',
331 route_name='user_delete', request_method='POST',
331 renderer='rhodecode:templates/admin/users/user_edit.mako')
332 renderer='rhodecode:templates/admin/users/user_edit.mako')
332 def user_delete(self):
333 def user_delete(self):
333 _ = self.request.translate
334 _ = self.request.translate
334 c = self.load_default_context()
335 c = self.load_default_context()
335 c.user = self.db_user
336 c.user = self.db_user
336
337
337 _repos = c.user.repositories
338 _repos = c.user.repositories
338 _repo_groups = c.user.repository_groups
339 _repo_groups = c.user.repository_groups
339 _user_groups = c.user.user_groups
340 _user_groups = c.user.user_groups
340
341
341 handle_repos = None
342 handle_repos = None
342 handle_repo_groups = None
343 handle_repo_groups = None
343 handle_user_groups = None
344 handle_user_groups = None
344 # dummy call for flash of handle
345 # dummy call for flash of handle
345 set_handle_flash_repos = lambda: None
346 set_handle_flash_repos = lambda: None
346 set_handle_flash_repo_groups = lambda: None
347 set_handle_flash_repo_groups = lambda: None
347 set_handle_flash_user_groups = lambda: None
348 set_handle_flash_user_groups = lambda: None
348
349
349 if _repos and self.request.POST.get('user_repos'):
350 if _repos and self.request.POST.get('user_repos'):
350 do = self.request.POST['user_repos']
351 do = self.request.POST['user_repos']
351 if do == 'detach':
352 if do == 'detach':
352 handle_repos = 'detach'
353 handle_repos = 'detach'
353 set_handle_flash_repos = lambda: h.flash(
354 set_handle_flash_repos = lambda: h.flash(
354 _('Detached %s repositories') % len(_repos),
355 _('Detached %s repositories') % len(_repos),
355 category='success')
356 category='success')
356 elif do == 'delete':
357 elif do == 'delete':
357 handle_repos = 'delete'
358 handle_repos = 'delete'
358 set_handle_flash_repos = lambda: h.flash(
359 set_handle_flash_repos = lambda: h.flash(
359 _('Deleted %s repositories') % len(_repos),
360 _('Deleted %s repositories') % len(_repos),
360 category='success')
361 category='success')
361
362
362 if _repo_groups and self.request.POST.get('user_repo_groups'):
363 if _repo_groups and self.request.POST.get('user_repo_groups'):
363 do = self.request.POST['user_repo_groups']
364 do = self.request.POST['user_repo_groups']
364 if do == 'detach':
365 if do == 'detach':
365 handle_repo_groups = 'detach'
366 handle_repo_groups = 'detach'
366 set_handle_flash_repo_groups = lambda: h.flash(
367 set_handle_flash_repo_groups = lambda: h.flash(
367 _('Detached %s repository groups') % len(_repo_groups),
368 _('Detached %s repository groups') % len(_repo_groups),
368 category='success')
369 category='success')
369 elif do == 'delete':
370 elif do == 'delete':
370 handle_repo_groups = 'delete'
371 handle_repo_groups = 'delete'
371 set_handle_flash_repo_groups = lambda: h.flash(
372 set_handle_flash_repo_groups = lambda: h.flash(
372 _('Deleted %s repository groups') % len(_repo_groups),
373 _('Deleted %s repository groups') % len(_repo_groups),
373 category='success')
374 category='success')
374
375
375 if _user_groups and self.request.POST.get('user_user_groups'):
376 if _user_groups and self.request.POST.get('user_user_groups'):
376 do = self.request.POST['user_user_groups']
377 do = self.request.POST['user_user_groups']
377 if do == 'detach':
378 if do == 'detach':
378 handle_user_groups = 'detach'
379 handle_user_groups = 'detach'
379 set_handle_flash_user_groups = lambda: h.flash(
380 set_handle_flash_user_groups = lambda: h.flash(
380 _('Detached %s user groups') % len(_user_groups),
381 _('Detached %s user groups') % len(_user_groups),
381 category='success')
382 category='success')
382 elif do == 'delete':
383 elif do == 'delete':
383 handle_user_groups = 'delete'
384 handle_user_groups = 'delete'
384 set_handle_flash_user_groups = lambda: h.flash(
385 set_handle_flash_user_groups = lambda: h.flash(
385 _('Deleted %s user groups') % len(_user_groups),
386 _('Deleted %s user groups') % len(_user_groups),
386 category='success')
387 category='success')
387
388
388 old_values = c.user.get_api_data()
389 old_values = c.user.get_api_data()
389 try:
390 try:
390 UserModel().delete(c.user, handle_repos=handle_repos,
391 UserModel().delete(c.user, handle_repos=handle_repos,
391 handle_repo_groups=handle_repo_groups,
392 handle_repo_groups=handle_repo_groups,
392 handle_user_groups=handle_user_groups)
393 handle_user_groups=handle_user_groups)
393
394
394 audit_logger.store_web(
395 audit_logger.store_web(
395 'user.delete', action_data={'old_data': old_values},
396 'user.delete', action_data={'old_data': old_values},
396 user=c.rhodecode_user)
397 user=c.rhodecode_user)
397
398
398 Session().commit()
399 Session().commit()
399 set_handle_flash_repos()
400 set_handle_flash_repos()
400 set_handle_flash_repo_groups()
401 set_handle_flash_repo_groups()
401 set_handle_flash_user_groups()
402 set_handle_flash_user_groups()
402 h.flash(_('Successfully deleted user'), category='success')
403 h.flash(_('Successfully deleted user'), category='success')
403 except (UserOwnsReposException, UserOwnsRepoGroupsException,
404 except (UserOwnsReposException, UserOwnsRepoGroupsException,
404 UserOwnsUserGroupsException, DefaultUserException) as e:
405 UserOwnsUserGroupsException, DefaultUserException) as e:
405 h.flash(e, category='warning')
406 h.flash(e, category='warning')
406 except Exception:
407 except Exception:
407 log.exception("Exception during deletion of user")
408 log.exception("Exception during deletion of user")
408 h.flash(_('An error occurred during deletion of user'),
409 h.flash(_('An error occurred during deletion of user'),
409 category='error')
410 category='error')
410 raise HTTPFound(h.route_path('users'))
411 raise HTTPFound(h.route_path('users'))
411
412
412 @LoginRequired()
413 @LoginRequired()
413 @HasPermissionAllDecorator('hg.admin')
414 @HasPermissionAllDecorator('hg.admin')
414 @view_config(
415 @view_config(
415 route_name='user_edit', request_method='GET',
416 route_name='user_edit', request_method='GET',
416 renderer='rhodecode:templates/admin/users/user_edit.mako')
417 renderer='rhodecode:templates/admin/users/user_edit.mako')
417 def user_edit(self):
418 def user_edit(self):
418 _ = self.request.translate
419 _ = self.request.translate
419 c = self.load_default_context()
420 c = self.load_default_context()
420 c.user = self.db_user
421 c.user = self.db_user
421
422
422 c.active = 'profile'
423 c.active = 'profile'
423 c.extern_type = c.user.extern_type
424 c.extern_type = c.user.extern_type
424 c.extern_name = c.user.extern_name
425 c.extern_name = c.user.extern_name
425 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
426 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
426
427
427 defaults = c.user.get_dict()
428 defaults = c.user.get_dict()
428 defaults.update({'language': c.user.user_data.get('language')})
429 defaults.update({'language': c.user.user_data.get('language')})
429
430
430 data = render(
431 data = render(
431 'rhodecode:templates/admin/users/user_edit.mako',
432 'rhodecode:templates/admin/users/user_edit.mako',
432 self._get_template_context(c), self.request)
433 self._get_template_context(c), self.request)
433 html = formencode.htmlfill.render(
434 html = formencode.htmlfill.render(
434 data,
435 data,
435 defaults=defaults,
436 defaults=defaults,
436 encoding="UTF-8",
437 encoding="UTF-8",
437 force_defaults=False
438 force_defaults=False
438 )
439 )
439 return Response(html)
440 return Response(html)
440
441
441 @LoginRequired()
442 @LoginRequired()
442 @HasPermissionAllDecorator('hg.admin')
443 @HasPermissionAllDecorator('hg.admin')
443 @view_config(
444 @view_config(
444 route_name='user_edit_advanced', request_method='GET',
445 route_name='user_edit_advanced', request_method='GET',
445 renderer='rhodecode:templates/admin/users/user_edit.mako')
446 renderer='rhodecode:templates/admin/users/user_edit.mako')
446 def user_edit_advanced(self):
447 def user_edit_advanced(self):
447 _ = self.request.translate
448 _ = self.request.translate
448 c = self.load_default_context()
449 c = self.load_default_context()
449
450
450 user_id = self.db_user_id
451 user_id = self.db_user_id
451 c.user = self.db_user
452 c.user = self.db_user
452
453
453 c.active = 'advanced'
454 c.active = 'advanced'
454 c.personal_repo_group = RepoGroup.get_user_personal_repo_group(user_id)
455 c.personal_repo_group = RepoGroup.get_user_personal_repo_group(user_id)
455 c.personal_repo_group_name = RepoGroupModel()\
456 c.personal_repo_group_name = RepoGroupModel()\
456 .get_personal_group_name(c.user)
457 .get_personal_group_name(c.user)
457
458
458 c.user_to_review_rules = sorted(
459 c.user_to_review_rules = sorted(
459 (x.user for x in c.user.user_review_rules),
460 (x.user for x in c.user.user_review_rules),
460 key=lambda u: u.username.lower())
461 key=lambda u: u.username.lower())
461
462
462 c.first_admin = User.get_first_super_admin()
463 c.first_admin = User.get_first_super_admin()
463 defaults = c.user.get_dict()
464 defaults = c.user.get_dict()
464
465
465 # Interim workaround if the user participated on any pull requests as a
466 # Interim workaround if the user participated on any pull requests as a
466 # reviewer.
467 # reviewer.
467 has_review = len(c.user.reviewer_pull_requests)
468 has_review = len(c.user.reviewer_pull_requests)
468 c.can_delete_user = not has_review
469 c.can_delete_user = not has_review
469 c.can_delete_user_message = ''
470 c.can_delete_user_message = ''
470 inactive_link = h.link_to(
471 inactive_link = h.link_to(
471 'inactive', h.route_path('user_edit', user_id=user_id, _anchor='active'))
472 'inactive', h.route_path('user_edit', user_id=user_id, _anchor='active'))
472 if has_review == 1:
473 if has_review == 1:
473 c.can_delete_user_message = h.literal(_(
474 c.can_delete_user_message = h.literal(_(
474 'The user participates as reviewer in {} pull request and '
475 'The user participates as reviewer in {} pull request and '
475 'cannot be deleted. \nYou can set the user to '
476 'cannot be deleted. \nYou can set the user to '
476 '"{}" instead of deleting it.').format(
477 '"{}" instead of deleting it.').format(
477 has_review, inactive_link))
478 has_review, inactive_link))
478 elif has_review:
479 elif has_review:
479 c.can_delete_user_message = h.literal(_(
480 c.can_delete_user_message = h.literal(_(
480 'The user participates as reviewer in {} pull requests and '
481 'The user participates as reviewer in {} pull requests and '
481 'cannot be deleted. \nYou can set the user to '
482 'cannot be deleted. \nYou can set the user to '
482 '"{}" instead of deleting it.').format(
483 '"{}" instead of deleting it.').format(
483 has_review, inactive_link))
484 has_review, inactive_link))
484
485
485 data = render(
486 data = render(
486 'rhodecode:templates/admin/users/user_edit.mako',
487 'rhodecode:templates/admin/users/user_edit.mako',
487 self._get_template_context(c), self.request)
488 self._get_template_context(c), self.request)
488 html = formencode.htmlfill.render(
489 html = formencode.htmlfill.render(
489 data,
490 data,
490 defaults=defaults,
491 defaults=defaults,
491 encoding="UTF-8",
492 encoding="UTF-8",
492 force_defaults=False
493 force_defaults=False
493 )
494 )
494 return Response(html)
495 return Response(html)
495
496
496 @LoginRequired()
497 @LoginRequired()
497 @HasPermissionAllDecorator('hg.admin')
498 @HasPermissionAllDecorator('hg.admin')
498 @view_config(
499 @view_config(
499 route_name='user_edit_global_perms', request_method='GET',
500 route_name='user_edit_global_perms', request_method='GET',
500 renderer='rhodecode:templates/admin/users/user_edit.mako')
501 renderer='rhodecode:templates/admin/users/user_edit.mako')
501 def user_edit_global_perms(self):
502 def user_edit_global_perms(self):
502 _ = self.request.translate
503 _ = self.request.translate
503 c = self.load_default_context()
504 c = self.load_default_context()
504 c.user = self.db_user
505 c.user = self.db_user
505
506
506 c.active = 'global_perms'
507 c.active = 'global_perms'
507
508
508 c.default_user = User.get_default_user()
509 c.default_user = User.get_default_user()
509 defaults = c.user.get_dict()
510 defaults = c.user.get_dict()
510 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
511 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
511 defaults.update(c.default_user.get_default_perms())
512 defaults.update(c.default_user.get_default_perms())
512 defaults.update(c.user.get_default_perms())
513 defaults.update(c.user.get_default_perms())
513
514
514 data = render(
515 data = render(
515 'rhodecode:templates/admin/users/user_edit.mako',
516 'rhodecode:templates/admin/users/user_edit.mako',
516 self._get_template_context(c), self.request)
517 self._get_template_context(c), self.request)
517 html = formencode.htmlfill.render(
518 html = formencode.htmlfill.render(
518 data,
519 data,
519 defaults=defaults,
520 defaults=defaults,
520 encoding="UTF-8",
521 encoding="UTF-8",
521 force_defaults=False
522 force_defaults=False
522 )
523 )
523 return Response(html)
524 return Response(html)
524
525
525 @LoginRequired()
526 @LoginRequired()
526 @HasPermissionAllDecorator('hg.admin')
527 @HasPermissionAllDecorator('hg.admin')
527 @CSRFRequired()
528 @CSRFRequired()
528 @view_config(
529 @view_config(
529 route_name='user_edit_global_perms_update', request_method='POST',
530 route_name='user_edit_global_perms_update', request_method='POST',
530 renderer='rhodecode:templates/admin/users/user_edit.mako')
531 renderer='rhodecode:templates/admin/users/user_edit.mako')
531 def user_edit_global_perms_update(self):
532 def user_edit_global_perms_update(self):
532 _ = self.request.translate
533 _ = self.request.translate
533 c = self.load_default_context()
534 c = self.load_default_context()
534
535
535 user_id = self.db_user_id
536 user_id = self.db_user_id
536 c.user = self.db_user
537 c.user = self.db_user
537
538
538 c.active = 'global_perms'
539 c.active = 'global_perms'
539 try:
540 try:
540 # first stage that verifies the checkbox
541 # first stage that verifies the checkbox
541 _form = UserIndividualPermissionsForm()
542 _form = UserIndividualPermissionsForm(self.request.translate)
542 form_result = _form.to_python(dict(self.request.POST))
543 form_result = _form.to_python(dict(self.request.POST))
543 inherit_perms = form_result['inherit_default_permissions']
544 inherit_perms = form_result['inherit_default_permissions']
544 c.user.inherit_default_permissions = inherit_perms
545 c.user.inherit_default_permissions = inherit_perms
545 Session().add(c.user)
546 Session().add(c.user)
546
547
547 if not inherit_perms:
548 if not inherit_perms:
548 # only update the individual ones if we un check the flag
549 # only update the individual ones if we un check the flag
549 _form = UserPermissionsForm(
550 _form = UserPermissionsForm(
551 self.request.translate,
550 [x[0] for x in c.repo_create_choices],
552 [x[0] for x in c.repo_create_choices],
551 [x[0] for x in c.repo_create_on_write_choices],
553 [x[0] for x in c.repo_create_on_write_choices],
552 [x[0] for x in c.repo_group_create_choices],
554 [x[0] for x in c.repo_group_create_choices],
553 [x[0] for x in c.user_group_create_choices],
555 [x[0] for x in c.user_group_create_choices],
554 [x[0] for x in c.fork_choices],
556 [x[0] for x in c.fork_choices],
555 [x[0] for x in c.inherit_default_permission_choices])()
557 [x[0] for x in c.inherit_default_permission_choices])()
556
558
557 form_result = _form.to_python(dict(self.request.POST))
559 form_result = _form.to_python(dict(self.request.POST))
558 form_result.update({'perm_user_id': c.user.user_id})
560 form_result.update({'perm_user_id': c.user.user_id})
559
561
560 PermissionModel().update_user_permissions(form_result)
562 PermissionModel().update_user_permissions(form_result)
561
563
562 # TODO(marcink): implement global permissions
564 # TODO(marcink): implement global permissions
563 # audit_log.store_web('user.edit.permissions')
565 # audit_log.store_web('user.edit.permissions')
564
566
565 Session().commit()
567 Session().commit()
566 h.flash(_('User global permissions updated successfully'),
568 h.flash(_('User global permissions updated successfully'),
567 category='success')
569 category='success')
568
570
569 except formencode.Invalid as errors:
571 except formencode.Invalid as errors:
570 data = render(
572 data = render(
571 'rhodecode:templates/admin/users/user_edit.mako',
573 'rhodecode:templates/admin/users/user_edit.mako',
572 self._get_template_context(c), self.request)
574 self._get_template_context(c), self.request)
573 html = formencode.htmlfill.render(
575 html = formencode.htmlfill.render(
574 data,
576 data,
575 defaults=errors.value,
577 defaults=errors.value,
576 errors=errors.error_dict or {},
578 errors=errors.error_dict or {},
577 prefix_error=False,
579 prefix_error=False,
578 encoding="UTF-8",
580 encoding="UTF-8",
579 force_defaults=False
581 force_defaults=False
580 )
582 )
581 return Response(html)
583 return Response(html)
582 except Exception:
584 except Exception:
583 log.exception("Exception during permissions saving")
585 log.exception("Exception during permissions saving")
584 h.flash(_('An error occurred during permissions saving'),
586 h.flash(_('An error occurred during permissions saving'),
585 category='error')
587 category='error')
586 raise HTTPFound(h.route_path('user_edit_global_perms', user_id=user_id))
588 raise HTTPFound(h.route_path('user_edit_global_perms', user_id=user_id))
587
589
588 @LoginRequired()
590 @LoginRequired()
589 @HasPermissionAllDecorator('hg.admin')
591 @HasPermissionAllDecorator('hg.admin')
590 @CSRFRequired()
592 @CSRFRequired()
591 @view_config(
593 @view_config(
592 route_name='user_force_password_reset', request_method='POST',
594 route_name='user_force_password_reset', request_method='POST',
593 renderer='rhodecode:templates/admin/users/user_edit.mako')
595 renderer='rhodecode:templates/admin/users/user_edit.mako')
594 def user_force_password_reset(self):
596 def user_force_password_reset(self):
595 """
597 """
596 toggle reset password flag for this user
598 toggle reset password flag for this user
597 """
599 """
598 _ = self.request.translate
600 _ = self.request.translate
599 c = self.load_default_context()
601 c = self.load_default_context()
600
602
601 user_id = self.db_user_id
603 user_id = self.db_user_id
602 c.user = self.db_user
604 c.user = self.db_user
603
605
604 try:
606 try:
605 old_value = c.user.user_data.get('force_password_change')
607 old_value = c.user.user_data.get('force_password_change')
606 c.user.update_userdata(force_password_change=not old_value)
608 c.user.update_userdata(force_password_change=not old_value)
607
609
608 if old_value:
610 if old_value:
609 msg = _('Force password change disabled for user')
611 msg = _('Force password change disabled for user')
610 audit_logger.store_web(
612 audit_logger.store_web(
611 'user.edit.password_reset.disabled',
613 'user.edit.password_reset.disabled',
612 user=c.rhodecode_user)
614 user=c.rhodecode_user)
613 else:
615 else:
614 msg = _('Force password change enabled for user')
616 msg = _('Force password change enabled for user')
615 audit_logger.store_web(
617 audit_logger.store_web(
616 'user.edit.password_reset.enabled',
618 'user.edit.password_reset.enabled',
617 user=c.rhodecode_user)
619 user=c.rhodecode_user)
618
620
619 Session().commit()
621 Session().commit()
620 h.flash(msg, category='success')
622 h.flash(msg, category='success')
621 except Exception:
623 except Exception:
622 log.exception("Exception during password reset for user")
624 log.exception("Exception during password reset for user")
623 h.flash(_('An error occurred during password reset for user'),
625 h.flash(_('An error occurred during password reset for user'),
624 category='error')
626 category='error')
625
627
626 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
628 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
627
629
628 @LoginRequired()
630 @LoginRequired()
629 @HasPermissionAllDecorator('hg.admin')
631 @HasPermissionAllDecorator('hg.admin')
630 @CSRFRequired()
632 @CSRFRequired()
631 @view_config(
633 @view_config(
632 route_name='user_create_personal_repo_group', request_method='POST',
634 route_name='user_create_personal_repo_group', request_method='POST',
633 renderer='rhodecode:templates/admin/users/user_edit.mako')
635 renderer='rhodecode:templates/admin/users/user_edit.mako')
634 def user_create_personal_repo_group(self):
636 def user_create_personal_repo_group(self):
635 """
637 """
636 Create personal repository group for this user
638 Create personal repository group for this user
637 """
639 """
638 from rhodecode.model.repo_group import RepoGroupModel
640 from rhodecode.model.repo_group import RepoGroupModel
639
641
640 _ = self.request.translate
642 _ = self.request.translate
641 c = self.load_default_context()
643 c = self.load_default_context()
642
644
643 user_id = self.db_user_id
645 user_id = self.db_user_id
644 c.user = self.db_user
646 c.user = self.db_user
645
647
646 personal_repo_group = RepoGroup.get_user_personal_repo_group(
648 personal_repo_group = RepoGroup.get_user_personal_repo_group(
647 c.user.user_id)
649 c.user.user_id)
648 if personal_repo_group:
650 if personal_repo_group:
649 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
651 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
650
652
651 personal_repo_group_name = RepoGroupModel().get_personal_group_name(
653 personal_repo_group_name = RepoGroupModel().get_personal_group_name(
652 c.user)
654 c.user)
653 named_personal_group = RepoGroup.get_by_group_name(
655 named_personal_group = RepoGroup.get_by_group_name(
654 personal_repo_group_name)
656 personal_repo_group_name)
655 try:
657 try:
656
658
657 if named_personal_group and named_personal_group.user_id == c.user.user_id:
659 if named_personal_group and named_personal_group.user_id == c.user.user_id:
658 # migrate the same named group, and mark it as personal
660 # migrate the same named group, and mark it as personal
659 named_personal_group.personal = True
661 named_personal_group.personal = True
660 Session().add(named_personal_group)
662 Session().add(named_personal_group)
661 Session().commit()
663 Session().commit()
662 msg = _('Linked repository group `%s` as personal' % (
664 msg = _('Linked repository group `%s` as personal' % (
663 personal_repo_group_name,))
665 personal_repo_group_name,))
664 h.flash(msg, category='success')
666 h.flash(msg, category='success')
665 elif not named_personal_group:
667 elif not named_personal_group:
666 RepoGroupModel().create_personal_repo_group(c.user)
668 RepoGroupModel().create_personal_repo_group(c.user)
667
669
668 msg = _('Created repository group `%s`' % (
670 msg = _('Created repository group `%s`' % (
669 personal_repo_group_name,))
671 personal_repo_group_name,))
670 h.flash(msg, category='success')
672 h.flash(msg, category='success')
671 else:
673 else:
672 msg = _('Repository group `%s` is already taken' % (
674 msg = _('Repository group `%s` is already taken' % (
673 personal_repo_group_name,))
675 personal_repo_group_name,))
674 h.flash(msg, category='warning')
676 h.flash(msg, category='warning')
675 except Exception:
677 except Exception:
676 log.exception("Exception during repository group creation")
678 log.exception("Exception during repository group creation")
677 msg = _(
679 msg = _(
678 'An error occurred during repository group creation for user')
680 'An error occurred during repository group creation for user')
679 h.flash(msg, category='error')
681 h.flash(msg, category='error')
680 Session().rollback()
682 Session().rollback()
681
683
682 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
684 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
683
685
684 @LoginRequired()
686 @LoginRequired()
685 @HasPermissionAllDecorator('hg.admin')
687 @HasPermissionAllDecorator('hg.admin')
686 @view_config(
688 @view_config(
687 route_name='edit_user_auth_tokens', request_method='GET',
689 route_name='edit_user_auth_tokens', request_method='GET',
688 renderer='rhodecode:templates/admin/users/user_edit.mako')
690 renderer='rhodecode:templates/admin/users/user_edit.mako')
689 def auth_tokens(self):
691 def auth_tokens(self):
690 _ = self.request.translate
692 _ = self.request.translate
691 c = self.load_default_context()
693 c = self.load_default_context()
692 c.user = self.db_user
694 c.user = self.db_user
693
695
694 c.active = 'auth_tokens'
696 c.active = 'auth_tokens'
695
697
696 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
698 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
697 c.role_values = [
699 c.role_values = [
698 (x, AuthTokenModel.cls._get_role_name(x))
700 (x, AuthTokenModel.cls._get_role_name(x))
699 for x in AuthTokenModel.cls.ROLES]
701 for x in AuthTokenModel.cls.ROLES]
700 c.role_options = [(c.role_values, _("Role"))]
702 c.role_options = [(c.role_values, _("Role"))]
701 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
703 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
702 c.user.user_id, show_expired=True)
704 c.user.user_id, show_expired=True)
703 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
705 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
704 return self._get_template_context(c)
706 return self._get_template_context(c)
705
707
706 def maybe_attach_token_scope(self, token):
708 def maybe_attach_token_scope(self, token):
707 # implemented in EE edition
709 # implemented in EE edition
708 pass
710 pass
709
711
710 @LoginRequired()
712 @LoginRequired()
711 @HasPermissionAllDecorator('hg.admin')
713 @HasPermissionAllDecorator('hg.admin')
712 @CSRFRequired()
714 @CSRFRequired()
713 @view_config(
715 @view_config(
714 route_name='edit_user_auth_tokens_add', request_method='POST')
716 route_name='edit_user_auth_tokens_add', request_method='POST')
715 def auth_tokens_add(self):
717 def auth_tokens_add(self):
716 _ = self.request.translate
718 _ = self.request.translate
717 c = self.load_default_context()
719 c = self.load_default_context()
718
720
719 user_id = self.db_user_id
721 user_id = self.db_user_id
720 c.user = self.db_user
722 c.user = self.db_user
721
723
722 user_data = c.user.get_api_data()
724 user_data = c.user.get_api_data()
723 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
725 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
724 description = self.request.POST.get('description')
726 description = self.request.POST.get('description')
725 role = self.request.POST.get('role')
727 role = self.request.POST.get('role')
726
728
727 token = AuthTokenModel().create(
729 token = AuthTokenModel().create(
728 c.user.user_id, description, lifetime, role)
730 c.user.user_id, description, lifetime, role)
729 token_data = token.get_api_data()
731 token_data = token.get_api_data()
730
732
731 self.maybe_attach_token_scope(token)
733 self.maybe_attach_token_scope(token)
732 audit_logger.store_web(
734 audit_logger.store_web(
733 'user.edit.token.add', action_data={
735 'user.edit.token.add', action_data={
734 'data': {'token': token_data, 'user': user_data}},
736 'data': {'token': token_data, 'user': user_data}},
735 user=self._rhodecode_user, )
737 user=self._rhodecode_user, )
736 Session().commit()
738 Session().commit()
737
739
738 h.flash(_("Auth token successfully created"), category='success')
740 h.flash(_("Auth token successfully created"), category='success')
739 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
741 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
740
742
741 @LoginRequired()
743 @LoginRequired()
742 @HasPermissionAllDecorator('hg.admin')
744 @HasPermissionAllDecorator('hg.admin')
743 @CSRFRequired()
745 @CSRFRequired()
744 @view_config(
746 @view_config(
745 route_name='edit_user_auth_tokens_delete', request_method='POST')
747 route_name='edit_user_auth_tokens_delete', request_method='POST')
746 def auth_tokens_delete(self):
748 def auth_tokens_delete(self):
747 _ = self.request.translate
749 _ = self.request.translate
748 c = self.load_default_context()
750 c = self.load_default_context()
749
751
750 user_id = self.db_user_id
752 user_id = self.db_user_id
751 c.user = self.db_user
753 c.user = self.db_user
752
754
753 user_data = c.user.get_api_data()
755 user_data = c.user.get_api_data()
754
756
755 del_auth_token = self.request.POST.get('del_auth_token')
757 del_auth_token = self.request.POST.get('del_auth_token')
756
758
757 if del_auth_token:
759 if del_auth_token:
758 token = UserApiKeys.get_or_404(del_auth_token)
760 token = UserApiKeys.get_or_404(del_auth_token)
759 token_data = token.get_api_data()
761 token_data = token.get_api_data()
760
762
761 AuthTokenModel().delete(del_auth_token, c.user.user_id)
763 AuthTokenModel().delete(del_auth_token, c.user.user_id)
762 audit_logger.store_web(
764 audit_logger.store_web(
763 'user.edit.token.delete', action_data={
765 'user.edit.token.delete', action_data={
764 'data': {'token': token_data, 'user': user_data}},
766 'data': {'token': token_data, 'user': user_data}},
765 user=self._rhodecode_user,)
767 user=self._rhodecode_user,)
766 Session().commit()
768 Session().commit()
767 h.flash(_("Auth token successfully deleted"), category='success')
769 h.flash(_("Auth token successfully deleted"), category='success')
768
770
769 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
771 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
770
772
771 @LoginRequired()
773 @LoginRequired()
772 @HasPermissionAllDecorator('hg.admin')
774 @HasPermissionAllDecorator('hg.admin')
773 @view_config(
775 @view_config(
774 route_name='edit_user_ssh_keys', request_method='GET',
776 route_name='edit_user_ssh_keys', request_method='GET',
775 renderer='rhodecode:templates/admin/users/user_edit.mako')
777 renderer='rhodecode:templates/admin/users/user_edit.mako')
776 def ssh_keys(self):
778 def ssh_keys(self):
777 _ = self.request.translate
779 _ = self.request.translate
778 c = self.load_default_context()
780 c = self.load_default_context()
779 c.user = self.db_user
781 c.user = self.db_user
780
782
781 c.active = 'ssh_keys'
783 c.active = 'ssh_keys'
782 c.default_key = self.request.GET.get('default_key')
784 c.default_key = self.request.GET.get('default_key')
783 c.user_ssh_keys = SshKeyModel().get_ssh_keys(c.user.user_id)
785 c.user_ssh_keys = SshKeyModel().get_ssh_keys(c.user.user_id)
784 return self._get_template_context(c)
786 return self._get_template_context(c)
785
787
786 @LoginRequired()
788 @LoginRequired()
787 @HasPermissionAllDecorator('hg.admin')
789 @HasPermissionAllDecorator('hg.admin')
788 @view_config(
790 @view_config(
789 route_name='edit_user_ssh_keys_generate_keypair', request_method='GET',
791 route_name='edit_user_ssh_keys_generate_keypair', request_method='GET',
790 renderer='rhodecode:templates/admin/users/user_edit.mako')
792 renderer='rhodecode:templates/admin/users/user_edit.mako')
791 def ssh_keys_generate_keypair(self):
793 def ssh_keys_generate_keypair(self):
792 _ = self.request.translate
794 _ = self.request.translate
793 c = self.load_default_context()
795 c = self.load_default_context()
794
796
795 c.user = self.db_user
797 c.user = self.db_user
796
798
797 c.active = 'ssh_keys_generate'
799 c.active = 'ssh_keys_generate'
798 comment = 'RhodeCode-SSH {}'.format(c.user.email or '')
800 comment = 'RhodeCode-SSH {}'.format(c.user.email or '')
799 c.private, c.public = SshKeyModel().generate_keypair(comment=comment)
801 c.private, c.public = SshKeyModel().generate_keypair(comment=comment)
800
802
801 return self._get_template_context(c)
803 return self._get_template_context(c)
802
804
803 @LoginRequired()
805 @LoginRequired()
804 @HasPermissionAllDecorator('hg.admin')
806 @HasPermissionAllDecorator('hg.admin')
805 @CSRFRequired()
807 @CSRFRequired()
806 @view_config(
808 @view_config(
807 route_name='edit_user_ssh_keys_add', request_method='POST')
809 route_name='edit_user_ssh_keys_add', request_method='POST')
808 def ssh_keys_add(self):
810 def ssh_keys_add(self):
809 _ = self.request.translate
811 _ = self.request.translate
810 c = self.load_default_context()
812 c = self.load_default_context()
811
813
812 user_id = self.db_user_id
814 user_id = self.db_user_id
813 c.user = self.db_user
815 c.user = self.db_user
814
816
815 user_data = c.user.get_api_data()
817 user_data = c.user.get_api_data()
816 key_data = self.request.POST.get('key_data')
818 key_data = self.request.POST.get('key_data')
817 description = self.request.POST.get('description')
819 description = self.request.POST.get('description')
818
820
819 try:
821 try:
820 if not key_data:
822 if not key_data:
821 raise ValueError('Please add a valid public key')
823 raise ValueError('Please add a valid public key')
822
824
823 key = SshKeyModel().parse_key(key_data.strip())
825 key = SshKeyModel().parse_key(key_data.strip())
824 fingerprint = key.hash_md5()
826 fingerprint = key.hash_md5()
825
827
826 ssh_key = SshKeyModel().create(
828 ssh_key = SshKeyModel().create(
827 c.user.user_id, fingerprint, key_data, description)
829 c.user.user_id, fingerprint, key_data, description)
828 ssh_key_data = ssh_key.get_api_data()
830 ssh_key_data = ssh_key.get_api_data()
829
831
830 audit_logger.store_web(
832 audit_logger.store_web(
831 'user.edit.ssh_key.add', action_data={
833 'user.edit.ssh_key.add', action_data={
832 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
834 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
833 user=self._rhodecode_user, )
835 user=self._rhodecode_user, )
834 Session().commit()
836 Session().commit()
835
837
836 # Trigger an event on change of keys.
838 # Trigger an event on change of keys.
837 trigger(SshKeyFileChangeEvent(), self.request.registry)
839 trigger(SshKeyFileChangeEvent(), self.request.registry)
838
840
839 h.flash(_("Ssh Key successfully created"), category='success')
841 h.flash(_("Ssh Key successfully created"), category='success')
840
842
841 except IntegrityError:
843 except IntegrityError:
842 log.exception("Exception during ssh key saving")
844 log.exception("Exception during ssh key saving")
843 h.flash(_('An error occurred during ssh key saving: {}').format(
845 h.flash(_('An error occurred during ssh key saving: {}').format(
844 'Such key already exists, please use a different one'),
846 'Such key already exists, please use a different one'),
845 category='error')
847 category='error')
846 except Exception as e:
848 except Exception as e:
847 log.exception("Exception during ssh key saving")
849 log.exception("Exception during ssh key saving")
848 h.flash(_('An error occurred during ssh key saving: {}').format(e),
850 h.flash(_('An error occurred during ssh key saving: {}').format(e),
849 category='error')
851 category='error')
850
852
851 return HTTPFound(
853 return HTTPFound(
852 h.route_path('edit_user_ssh_keys', user_id=user_id))
854 h.route_path('edit_user_ssh_keys', user_id=user_id))
853
855
854 @LoginRequired()
856 @LoginRequired()
855 @HasPermissionAllDecorator('hg.admin')
857 @HasPermissionAllDecorator('hg.admin')
856 @CSRFRequired()
858 @CSRFRequired()
857 @view_config(
859 @view_config(
858 route_name='edit_user_ssh_keys_delete', request_method='POST')
860 route_name='edit_user_ssh_keys_delete', request_method='POST')
859 def ssh_keys_delete(self):
861 def ssh_keys_delete(self):
860 _ = self.request.translate
862 _ = self.request.translate
861 c = self.load_default_context()
863 c = self.load_default_context()
862
864
863 user_id = self.db_user_id
865 user_id = self.db_user_id
864 c.user = self.db_user
866 c.user = self.db_user
865
867
866 user_data = c.user.get_api_data()
868 user_data = c.user.get_api_data()
867
869
868 del_ssh_key = self.request.POST.get('del_ssh_key')
870 del_ssh_key = self.request.POST.get('del_ssh_key')
869
871
870 if del_ssh_key:
872 if del_ssh_key:
871 ssh_key = UserSshKeys.get_or_404(del_ssh_key)
873 ssh_key = UserSshKeys.get_or_404(del_ssh_key)
872 ssh_key_data = ssh_key.get_api_data()
874 ssh_key_data = ssh_key.get_api_data()
873
875
874 SshKeyModel().delete(del_ssh_key, c.user.user_id)
876 SshKeyModel().delete(del_ssh_key, c.user.user_id)
875 audit_logger.store_web(
877 audit_logger.store_web(
876 'user.edit.ssh_key.delete', action_data={
878 'user.edit.ssh_key.delete', action_data={
877 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
879 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
878 user=self._rhodecode_user,)
880 user=self._rhodecode_user,)
879 Session().commit()
881 Session().commit()
880 # Trigger an event on change of keys.
882 # Trigger an event on change of keys.
881 trigger(SshKeyFileChangeEvent(), self.request.registry)
883 trigger(SshKeyFileChangeEvent(), self.request.registry)
882 h.flash(_("Ssh key successfully deleted"), category='success')
884 h.flash(_("Ssh key successfully deleted"), category='success')
883
885
884 return HTTPFound(h.route_path('edit_user_ssh_keys', user_id=user_id))
886 return HTTPFound(h.route_path('edit_user_ssh_keys', user_id=user_id))
885
887
886 @LoginRequired()
888 @LoginRequired()
887 @HasPermissionAllDecorator('hg.admin')
889 @HasPermissionAllDecorator('hg.admin')
888 @view_config(
890 @view_config(
889 route_name='edit_user_emails', request_method='GET',
891 route_name='edit_user_emails', request_method='GET',
890 renderer='rhodecode:templates/admin/users/user_edit.mako')
892 renderer='rhodecode:templates/admin/users/user_edit.mako')
891 def emails(self):
893 def emails(self):
892 _ = self.request.translate
894 _ = self.request.translate
893 c = self.load_default_context()
895 c = self.load_default_context()
894 c.user = self.db_user
896 c.user = self.db_user
895
897
896 c.active = 'emails'
898 c.active = 'emails'
897 c.user_email_map = UserEmailMap.query() \
899 c.user_email_map = UserEmailMap.query() \
898 .filter(UserEmailMap.user == c.user).all()
900 .filter(UserEmailMap.user == c.user).all()
899
901
900 return self._get_template_context(c)
902 return self._get_template_context(c)
901
903
902 @LoginRequired()
904 @LoginRequired()
903 @HasPermissionAllDecorator('hg.admin')
905 @HasPermissionAllDecorator('hg.admin')
904 @CSRFRequired()
906 @CSRFRequired()
905 @view_config(
907 @view_config(
906 route_name='edit_user_emails_add', request_method='POST')
908 route_name='edit_user_emails_add', request_method='POST')
907 def emails_add(self):
909 def emails_add(self):
908 _ = self.request.translate
910 _ = self.request.translate
909 c = self.load_default_context()
911 c = self.load_default_context()
910
912
911 user_id = self.db_user_id
913 user_id = self.db_user_id
912 c.user = self.db_user
914 c.user = self.db_user
913
915
914 email = self.request.POST.get('new_email')
916 email = self.request.POST.get('new_email')
915 user_data = c.user.get_api_data()
917 user_data = c.user.get_api_data()
916 try:
918 try:
919
920 form = UserExtraEmailForm(self.request.translate)()
921 data = form.to_python({'email': email})
922 email = data['email']
923
917 UserModel().add_extra_email(c.user.user_id, email)
924 UserModel().add_extra_email(c.user.user_id, email)
918 audit_logger.store_web(
925 audit_logger.store_web(
919 'user.edit.email.add',
926 'user.edit.email.add',
920 action_data={'email': email, 'user': user_data},
927 action_data={'email': email, 'user': user_data},
921 user=self._rhodecode_user)
928 user=self._rhodecode_user)
922 Session().commit()
929 Session().commit()
923 h.flash(_("Added new email address `%s` for user account") % email,
930 h.flash(_("Added new email address `%s` for user account") % email,
924 category='success')
931 category='success')
925 except formencode.Invalid as error:
932 except formencode.Invalid as error:
926 h.flash(h.escape(error.error_dict['email']), category='error')
933 h.flash(h.escape(error.error_dict['email']), category='error')
927 except IntegrityError:
934 except IntegrityError:
928 log.warning("Email %s already exists", email)
935 log.warning("Email %s already exists", email)
929 h.flash(_('Email `{}` is already registered for another user.').format(email),
936 h.flash(_('Email `{}` is already registered for another user.').format(email),
930 category='error')
937 category='error')
931 except Exception:
938 except Exception:
932 log.exception("Exception during email saving")
939 log.exception("Exception during email saving")
933 h.flash(_('An error occurred during email saving'),
940 h.flash(_('An error occurred during email saving'),
934 category='error')
941 category='error')
935 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
942 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
936
943
937 @LoginRequired()
944 @LoginRequired()
938 @HasPermissionAllDecorator('hg.admin')
945 @HasPermissionAllDecorator('hg.admin')
939 @CSRFRequired()
946 @CSRFRequired()
940 @view_config(
947 @view_config(
941 route_name='edit_user_emails_delete', request_method='POST')
948 route_name='edit_user_emails_delete', request_method='POST')
942 def emails_delete(self):
949 def emails_delete(self):
943 _ = self.request.translate
950 _ = self.request.translate
944 c = self.load_default_context()
951 c = self.load_default_context()
945
952
946 user_id = self.db_user_id
953 user_id = self.db_user_id
947 c.user = self.db_user
954 c.user = self.db_user
948
955
949 email_id = self.request.POST.get('del_email_id')
956 email_id = self.request.POST.get('del_email_id')
950 user_model = UserModel()
957 user_model = UserModel()
951
958
952 email = UserEmailMap.query().get(email_id).email
959 email = UserEmailMap.query().get(email_id).email
953 user_data = c.user.get_api_data()
960 user_data = c.user.get_api_data()
954 user_model.delete_extra_email(c.user.user_id, email_id)
961 user_model.delete_extra_email(c.user.user_id, email_id)
955 audit_logger.store_web(
962 audit_logger.store_web(
956 'user.edit.email.delete',
963 'user.edit.email.delete',
957 action_data={'email': email, 'user': user_data},
964 action_data={'email': email, 'user': user_data},
958 user=self._rhodecode_user)
965 user=self._rhodecode_user)
959 Session().commit()
966 Session().commit()
960 h.flash(_("Removed email address from user account"),
967 h.flash(_("Removed email address from user account"),
961 category='success')
968 category='success')
962 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
969 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
963
970
964 @LoginRequired()
971 @LoginRequired()
965 @HasPermissionAllDecorator('hg.admin')
972 @HasPermissionAllDecorator('hg.admin')
966 @view_config(
973 @view_config(
967 route_name='edit_user_ips', request_method='GET',
974 route_name='edit_user_ips', request_method='GET',
968 renderer='rhodecode:templates/admin/users/user_edit.mako')
975 renderer='rhodecode:templates/admin/users/user_edit.mako')
969 def ips(self):
976 def ips(self):
970 _ = self.request.translate
977 _ = self.request.translate
971 c = self.load_default_context()
978 c = self.load_default_context()
972 c.user = self.db_user
979 c.user = self.db_user
973
980
974 c.active = 'ips'
981 c.active = 'ips'
975 c.user_ip_map = UserIpMap.query() \
982 c.user_ip_map = UserIpMap.query() \
976 .filter(UserIpMap.user == c.user).all()
983 .filter(UserIpMap.user == c.user).all()
977
984
978 c.inherit_default_ips = c.user.inherit_default_permissions
985 c.inherit_default_ips = c.user.inherit_default_permissions
979 c.default_user_ip_map = UserIpMap.query() \
986 c.default_user_ip_map = UserIpMap.query() \
980 .filter(UserIpMap.user == User.get_default_user()).all()
987 .filter(UserIpMap.user == User.get_default_user()).all()
981
988
982 return self._get_template_context(c)
989 return self._get_template_context(c)
983
990
984 @LoginRequired()
991 @LoginRequired()
985 @HasPermissionAllDecorator('hg.admin')
992 @HasPermissionAllDecorator('hg.admin')
986 @CSRFRequired()
993 @CSRFRequired()
987 @view_config(
994 @view_config(
988 route_name='edit_user_ips_add', request_method='POST')
995 route_name='edit_user_ips_add', request_method='POST')
989 # NOTE(marcink): this view is allowed for default users, as we can
996 # NOTE(marcink): this view is allowed for default users, as we can
990 # edit their IP white list
997 # edit their IP white list
991 def ips_add(self):
998 def ips_add(self):
992 _ = self.request.translate
999 _ = self.request.translate
993 c = self.load_default_context()
1000 c = self.load_default_context()
994
1001
995 user_id = self.db_user_id
1002 user_id = self.db_user_id
996 c.user = self.db_user
1003 c.user = self.db_user
997
1004
998 user_model = UserModel()
1005 user_model = UserModel()
999 desc = self.request.POST.get('description')
1006 desc = self.request.POST.get('description')
1000 try:
1007 try:
1001 ip_list = user_model.parse_ip_range(
1008 ip_list = user_model.parse_ip_range(
1002 self.request.POST.get('new_ip'))
1009 self.request.POST.get('new_ip'))
1003 except Exception as e:
1010 except Exception as e:
1004 ip_list = []
1011 ip_list = []
1005 log.exception("Exception during ip saving")
1012 log.exception("Exception during ip saving")
1006 h.flash(_('An error occurred during ip saving:%s' % (e,)),
1013 h.flash(_('An error occurred during ip saving:%s' % (e,)),
1007 category='error')
1014 category='error')
1008 added = []
1015 added = []
1009 user_data = c.user.get_api_data()
1016 user_data = c.user.get_api_data()
1010 for ip in ip_list:
1017 for ip in ip_list:
1011 try:
1018 try:
1019 form = UserExtraIpForm(self.request.translate)()
1020 data = form.to_python({'ip': ip})
1021 ip = data['ip']
1022
1012 user_model.add_extra_ip(c.user.user_id, ip, desc)
1023 user_model.add_extra_ip(c.user.user_id, ip, desc)
1013 audit_logger.store_web(
1024 audit_logger.store_web(
1014 'user.edit.ip.add',
1025 'user.edit.ip.add',
1015 action_data={'ip': ip, 'user': user_data},
1026 action_data={'ip': ip, 'user': user_data},
1016 user=self._rhodecode_user)
1027 user=self._rhodecode_user)
1017 Session().commit()
1028 Session().commit()
1018 added.append(ip)
1029 added.append(ip)
1019 except formencode.Invalid as error:
1030 except formencode.Invalid as error:
1020 msg = error.error_dict['ip']
1031 msg = error.error_dict['ip']
1021 h.flash(msg, category='error')
1032 h.flash(msg, category='error')
1022 except Exception:
1033 except Exception:
1023 log.exception("Exception during ip saving")
1034 log.exception("Exception during ip saving")
1024 h.flash(_('An error occurred during ip saving'),
1035 h.flash(_('An error occurred during ip saving'),
1025 category='error')
1036 category='error')
1026 if added:
1037 if added:
1027 h.flash(
1038 h.flash(
1028 _("Added ips %s to user whitelist") % (', '.join(ip_list), ),
1039 _("Added ips %s to user whitelist") % (', '.join(ip_list), ),
1029 category='success')
1040 category='success')
1030 if 'default_user' in self.request.POST:
1041 if 'default_user' in self.request.POST:
1031 # case for editing global IP list we do it for 'DEFAULT' user
1042 # case for editing global IP list we do it for 'DEFAULT' user
1032 raise HTTPFound(h.route_path('admin_permissions_ips'))
1043 raise HTTPFound(h.route_path('admin_permissions_ips'))
1033 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1044 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1034
1045
1035 @LoginRequired()
1046 @LoginRequired()
1036 @HasPermissionAllDecorator('hg.admin')
1047 @HasPermissionAllDecorator('hg.admin')
1037 @CSRFRequired()
1048 @CSRFRequired()
1038 @view_config(
1049 @view_config(
1039 route_name='edit_user_ips_delete', request_method='POST')
1050 route_name='edit_user_ips_delete', request_method='POST')
1040 # NOTE(marcink): this view is allowed for default users, as we can
1051 # NOTE(marcink): this view is allowed for default users, as we can
1041 # edit their IP white list
1052 # edit their IP white list
1042 def ips_delete(self):
1053 def ips_delete(self):
1043 _ = self.request.translate
1054 _ = self.request.translate
1044 c = self.load_default_context()
1055 c = self.load_default_context()
1045
1056
1046 user_id = self.db_user_id
1057 user_id = self.db_user_id
1047 c.user = self.db_user
1058 c.user = self.db_user
1048
1059
1049 ip_id = self.request.POST.get('del_ip_id')
1060 ip_id = self.request.POST.get('del_ip_id')
1050 user_model = UserModel()
1061 user_model = UserModel()
1051 user_data = c.user.get_api_data()
1062 user_data = c.user.get_api_data()
1052 ip = UserIpMap.query().get(ip_id).ip_addr
1063 ip = UserIpMap.query().get(ip_id).ip_addr
1053 user_model.delete_extra_ip(c.user.user_id, ip_id)
1064 user_model.delete_extra_ip(c.user.user_id, ip_id)
1054 audit_logger.store_web(
1065 audit_logger.store_web(
1055 'user.edit.ip.delete', action_data={'ip': ip, 'user': user_data},
1066 'user.edit.ip.delete', action_data={'ip': ip, 'user': user_data},
1056 user=self._rhodecode_user)
1067 user=self._rhodecode_user)
1057 Session().commit()
1068 Session().commit()
1058 h.flash(_("Removed ip address from user whitelist"), category='success')
1069 h.flash(_("Removed ip address from user whitelist"), category='success')
1059
1070
1060 if 'default_user' in self.request.POST:
1071 if 'default_user' in self.request.POST:
1061 # case for editing global IP list we do it for 'DEFAULT' user
1072 # case for editing global IP list we do it for 'DEFAULT' user
1062 raise HTTPFound(h.route_path('admin_permissions_ips'))
1073 raise HTTPFound(h.route_path('admin_permissions_ips'))
1063 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1074 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1064
1075
1065 @LoginRequired()
1076 @LoginRequired()
1066 @HasPermissionAllDecorator('hg.admin')
1077 @HasPermissionAllDecorator('hg.admin')
1067 @view_config(
1078 @view_config(
1068 route_name='edit_user_groups_management', request_method='GET',
1079 route_name='edit_user_groups_management', request_method='GET',
1069 renderer='rhodecode:templates/admin/users/user_edit.mako')
1080 renderer='rhodecode:templates/admin/users/user_edit.mako')
1070 def groups_management(self):
1081 def groups_management(self):
1071 c = self.load_default_context()
1082 c = self.load_default_context()
1072 c.user = self.db_user
1083 c.user = self.db_user
1073 c.data = c.user.group_member
1084 c.data = c.user.group_member
1074
1085
1075 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
1086 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
1076 for group in c.user.group_member]
1087 for group in c.user.group_member]
1077 c.groups = json.dumps(groups)
1088 c.groups = json.dumps(groups)
1078 c.active = 'groups'
1089 c.active = 'groups'
1079
1090
1080 return self._get_template_context(c)
1091 return self._get_template_context(c)
1081
1092
1082 @LoginRequired()
1093 @LoginRequired()
1083 @HasPermissionAllDecorator('hg.admin')
1094 @HasPermissionAllDecorator('hg.admin')
1084 @CSRFRequired()
1095 @CSRFRequired()
1085 @view_config(
1096 @view_config(
1086 route_name='edit_user_groups_management_updates', request_method='POST')
1097 route_name='edit_user_groups_management_updates', request_method='POST')
1087 def groups_management_updates(self):
1098 def groups_management_updates(self):
1088 _ = self.request.translate
1099 _ = self.request.translate
1089 c = self.load_default_context()
1100 c = self.load_default_context()
1090
1101
1091 user_id = self.db_user_id
1102 user_id = self.db_user_id
1092 c.user = self.db_user
1103 c.user = self.db_user
1093
1104
1094 user_groups = set(self.request.POST.getall('users_group_id'))
1105 user_groups = set(self.request.POST.getall('users_group_id'))
1095 user_groups_objects = []
1106 user_groups_objects = []
1096
1107
1097 for ugid in user_groups:
1108 for ugid in user_groups:
1098 user_groups_objects.append(
1109 user_groups_objects.append(
1099 UserGroupModel().get_group(safe_int(ugid)))
1110 UserGroupModel().get_group(safe_int(ugid)))
1100 user_group_model = UserGroupModel()
1111 user_group_model = UserGroupModel()
1101 added_to_groups, removed_from_groups = \
1112 added_to_groups, removed_from_groups = \
1102 user_group_model.change_groups(c.user, user_groups_objects)
1113 user_group_model.change_groups(c.user, user_groups_objects)
1103
1114
1104 user_data = c.user.get_api_data()
1115 user_data = c.user.get_api_data()
1105 for user_group_id in added_to_groups:
1116 for user_group_id in added_to_groups:
1106 user_group = UserGroup.get(user_group_id)
1117 user_group = UserGroup.get(user_group_id)
1107 old_values = user_group.get_api_data()
1118 old_values = user_group.get_api_data()
1108 audit_logger.store_web(
1119 audit_logger.store_web(
1109 'user_group.edit.member.add',
1120 'user_group.edit.member.add',
1110 action_data={'user': user_data, 'old_data': old_values},
1121 action_data={'user': user_data, 'old_data': old_values},
1111 user=self._rhodecode_user)
1122 user=self._rhodecode_user)
1112
1123
1113 for user_group_id in removed_from_groups:
1124 for user_group_id in removed_from_groups:
1114 user_group = UserGroup.get(user_group_id)
1125 user_group = UserGroup.get(user_group_id)
1115 old_values = user_group.get_api_data()
1126 old_values = user_group.get_api_data()
1116 audit_logger.store_web(
1127 audit_logger.store_web(
1117 'user_group.edit.member.delete',
1128 'user_group.edit.member.delete',
1118 action_data={'user': user_data, 'old_data': old_values},
1129 action_data={'user': user_data, 'old_data': old_values},
1119 user=self._rhodecode_user)
1130 user=self._rhodecode_user)
1120
1131
1121 Session().commit()
1132 Session().commit()
1122 c.active = 'user_groups_management'
1133 c.active = 'user_groups_management'
1123 h.flash(_("Groups successfully changed"), category='success')
1134 h.flash(_("Groups successfully changed"), category='success')
1124
1135
1125 return HTTPFound(h.route_path(
1136 return HTTPFound(h.route_path(
1126 'edit_user_groups_management', user_id=user_id))
1137 'edit_user_groups_management', user_id=user_id))
1127
1138
1128 @LoginRequired()
1139 @LoginRequired()
1129 @HasPermissionAllDecorator('hg.admin')
1140 @HasPermissionAllDecorator('hg.admin')
1130 @view_config(
1141 @view_config(
1131 route_name='edit_user_audit_logs', request_method='GET',
1142 route_name='edit_user_audit_logs', request_method='GET',
1132 renderer='rhodecode:templates/admin/users/user_edit.mako')
1143 renderer='rhodecode:templates/admin/users/user_edit.mako')
1133 def user_audit_logs(self):
1144 def user_audit_logs(self):
1134 _ = self.request.translate
1145 _ = self.request.translate
1135 c = self.load_default_context()
1146 c = self.load_default_context()
1136 c.user = self.db_user
1147 c.user = self.db_user
1137
1148
1138 c.active = 'audit'
1149 c.active = 'audit'
1139
1150
1140 p = safe_int(self.request.GET.get('page', 1), 1)
1151 p = safe_int(self.request.GET.get('page', 1), 1)
1141
1152
1142 filter_term = self.request.GET.get('filter')
1153 filter_term = self.request.GET.get('filter')
1143 user_log = UserModel().get_user_log(c.user, filter_term)
1154 user_log = UserModel().get_user_log(c.user, filter_term)
1144
1155
1145 def url_generator(**kw):
1156 def url_generator(**kw):
1146 if filter_term:
1157 if filter_term:
1147 kw['filter'] = filter_term
1158 kw['filter'] = filter_term
1148 return self.request.current_route_path(_query=kw)
1159 return self.request.current_route_path(_query=kw)
1149
1160
1150 c.audit_logs = h.Page(
1161 c.audit_logs = h.Page(
1151 user_log, page=p, items_per_page=10, url=url_generator)
1162 user_log, page=p, items_per_page=10, url=url_generator)
1152 c.filter_term = filter_term
1163 c.filter_term = filter_term
1153 return self._get_template_context(c)
1164 return self._get_template_context(c)
1154
1165
1155 @LoginRequired()
1166 @LoginRequired()
1156 @HasPermissionAllDecorator('hg.admin')
1167 @HasPermissionAllDecorator('hg.admin')
1157 @view_config(
1168 @view_config(
1158 route_name='edit_user_perms_summary', request_method='GET',
1169 route_name='edit_user_perms_summary', request_method='GET',
1159 renderer='rhodecode:templates/admin/users/user_edit.mako')
1170 renderer='rhodecode:templates/admin/users/user_edit.mako')
1160 def user_perms_summary(self):
1171 def user_perms_summary(self):
1161 _ = self.request.translate
1172 _ = self.request.translate
1162 c = self.load_default_context()
1173 c = self.load_default_context()
1163 c.user = self.db_user
1174 c.user = self.db_user
1164
1175
1165 c.active = 'perms_summary'
1176 c.active = 'perms_summary'
1166 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1177 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1167
1178
1168 return self._get_template_context(c)
1179 return self._get_template_context(c)
1169
1180
1170 @LoginRequired()
1181 @LoginRequired()
1171 @HasPermissionAllDecorator('hg.admin')
1182 @HasPermissionAllDecorator('hg.admin')
1172 @view_config(
1183 @view_config(
1173 route_name='edit_user_perms_summary_json', request_method='GET',
1184 route_name='edit_user_perms_summary_json', request_method='GET',
1174 renderer='json_ext')
1185 renderer='json_ext')
1175 def user_perms_summary_json(self):
1186 def user_perms_summary_json(self):
1176 self.load_default_context()
1187 self.load_default_context()
1177 perm_user = self.db_user.AuthUser(ip_addr=self.request.remote_addr)
1188 perm_user = self.db_user.AuthUser(ip_addr=self.request.remote_addr)
1178
1189
1179 return perm_user.permissions
1190 return perm_user.permissions
@@ -1,167 +1,169 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 import uuid
22 import uuid
23
23
24 from pyramid.view import view_config
24 from pyramid.view import view_config
25 from pyramid.httpexceptions import HTTPBadRequest, HTTPForbidden, HTTPBadGateway
25 from pyramid.httpexceptions import HTTPBadRequest, HTTPForbidden, HTTPBadGateway
26
26
27 from rhodecode.apps._base import BaseAppView
27 from rhodecode.lib.channelstream import (
28 from rhodecode.lib.channelstream import (
28 channelstream_request,
29 channelstream_request,
29 ChannelstreamConnectionException,
30 ChannelstreamConnectionException,
30 ChannelstreamPermissionException,
31 ChannelstreamPermissionException,
31 check_channel_permissions,
32 check_channel_permissions,
32 get_connection_validators,
33 get_connection_validators,
33 get_user_data,
34 get_user_data,
34 parse_channels_info,
35 parse_channels_info,
35 update_history_from_logs,
36 update_history_from_logs,
36 STATE_PUBLIC_KEYS)
37 STATE_PUBLIC_KEYS)
38
37 from rhodecode.lib.auth import NotAnonymous
39 from rhodecode.lib.auth import NotAnonymous
38
40
39 log = logging.getLogger(__name__)
41 log = logging.getLogger(__name__)
40
42
41
43
42 class ChannelstreamView(object):
44 class ChannelstreamView(BaseAppView):
43 def __init__(self, context, request):
44 self.context = context
45 self.request = request
46
45
47 # Some of the decorators rely on this attribute to be present
46 def load_default_context(self):
48 # on the class of the decorated method.
47 c = self._get_local_tmpl_context()
49 self._rhodecode_user = request.user
48 self.channelstream_config = \
50 registry = request.registry
49 self.request.registry.rhodecode_plugins['channelstream']
51 self.channelstream_config = registry.rhodecode_plugins['channelstream']
52 if not self.channelstream_config.get('enabled'):
50 if not self.channelstream_config.get('enabled'):
53 log.error('Channelstream plugin is disabled')
51 log.error('Channelstream plugin is disabled')
54 raise HTTPBadRequest()
52 raise HTTPBadRequest()
55
53
54 return c
55
56 @NotAnonymous()
56 @NotAnonymous()
57 @view_config(route_name='channelstream_connect', renderer='json')
57 @view_config(route_name='channelstream_connect', renderer='json_ext')
58 def connect(self):
58 def connect(self):
59 self.load_default_context()
59 """ handle authorization of users trying to connect """
60 """ handle authorization of users trying to connect """
60 try:
61 try:
61 json_body = self.request.json_body
62 json_body = self.request.json_body
62 except Exception:
63 except Exception:
63 log.exception('Failed to decode json from request')
64 log.exception('Failed to decode json from request')
64 raise HTTPBadRequest()
65 raise HTTPBadRequest()
65
66
66 try:
67 try:
67 channels = check_channel_permissions(
68 channels = check_channel_permissions(
68 json_body.get('channels'),
69 json_body.get('channels'),
69 get_connection_validators(self.request.registry))
70 get_connection_validators(self.request.registry))
70 except ChannelstreamPermissionException:
71 except ChannelstreamPermissionException:
71 log.error('Incorrect permissions for requested channels')
72 log.error('Incorrect permissions for requested channels')
72 raise HTTPForbidden()
73 raise HTTPForbidden()
73
74
74 user = self._rhodecode_user
75 user = self._rhodecode_user
75 if user.user_id:
76 if user.user_id:
76 user_data = get_user_data(user.user_id)
77 user_data = get_user_data(user.user_id)
77 else:
78 else:
78 user_data = {
79 user_data = {
79 'id': None,
80 'id': None,
80 'username': None,
81 'username': None,
81 'first_name': None,
82 'first_name': None,
82 'last_name': None,
83 'last_name': None,
83 'icon_link': None,
84 'icon_link': None,
84 'display_name': None,
85 'display_name': None,
85 'display_link': None,
86 'display_link': None,
86 }
87 }
87 user_data['permissions'] = self._rhodecode_user.permissions_safe
88 user_data['permissions'] = self._rhodecode_user.permissions_safe
88 payload = {
89 payload = {
89 'username': user.username,
90 'username': user.username,
90 'user_state': user_data,
91 'user_state': user_data,
91 'conn_id': str(uuid.uuid4()),
92 'conn_id': str(uuid.uuid4()),
92 'channels': channels,
93 'channels': channels,
93 'channel_configs': {},
94 'channel_configs': {},
94 'state_public_keys': STATE_PUBLIC_KEYS,
95 'state_public_keys': STATE_PUBLIC_KEYS,
95 'info': {
96 'info': {
96 'exclude_channels': ['broadcast']
97 'exclude_channels': ['broadcast']
97 }
98 }
98 }
99 }
99 filtered_channels = [channel for channel in channels
100 filtered_channels = [channel for channel in channels
100 if channel != 'broadcast']
101 if channel != 'broadcast']
101 for channel in filtered_channels:
102 for channel in filtered_channels:
102 payload['channel_configs'][channel] = {
103 payload['channel_configs'][channel] = {
103 'notify_presence': True,
104 'notify_presence': True,
104 'history_size': 100,
105 'history_size': 100,
105 'store_history': True,
106 'store_history': True,
106 'broadcast_presence_with_user_lists': True
107 'broadcast_presence_with_user_lists': True
107 }
108 }
108 # connect user to server
109 # connect user to server
109 try:
110 try:
110 connect_result = channelstream_request(self.channelstream_config,
111 connect_result = channelstream_request(self.channelstream_config,
111 payload, '/connect')
112 payload, '/connect')
112 except ChannelstreamConnectionException:
113 except ChannelstreamConnectionException:
113 log.exception('Channelstream service is down')
114 log.exception('Channelstream service is down')
114 return HTTPBadGateway()
115 return HTTPBadGateway()
115
116
116 connect_result['channels'] = channels
117 connect_result['channels'] = channels
117 connect_result['channels_info'] = parse_channels_info(
118 connect_result['channels_info'] = parse_channels_info(
118 connect_result['channels_info'],
119 connect_result['channels_info'],
119 include_channel_info=filtered_channels)
120 include_channel_info=filtered_channels)
120 update_history_from_logs(self.channelstream_config,
121 update_history_from_logs(self.channelstream_config,
121 filtered_channels, connect_result)
122 filtered_channels, connect_result)
122 return connect_result
123 return connect_result
123
124
124 @NotAnonymous()
125 @NotAnonymous()
125 @view_config(route_name='channelstream_subscribe', renderer='json')
126 @view_config(route_name='channelstream_subscribe', renderer='json_ext')
126 def subscribe(self):
127 def subscribe(self):
127 """ can be used to subscribe specific connection to other channels """
128 """ can be used to subscribe specific connection to other channels """
129 self.load_default_context()
128 try:
130 try:
129 json_body = self.request.json_body
131 json_body = self.request.json_body
130 except Exception:
132 except Exception:
131 log.exception('Failed to decode json from request')
133 log.exception('Failed to decode json from request')
132 raise HTTPBadRequest()
134 raise HTTPBadRequest()
133 try:
135 try:
134 channels = check_channel_permissions(
136 channels = check_channel_permissions(
135 json_body.get('channels'),
137 json_body.get('channels'),
136 get_connection_validators(self.request.registry))
138 get_connection_validators(self.request.registry))
137 except ChannelstreamPermissionException:
139 except ChannelstreamPermissionException:
138 log.error('Incorrect permissions for requested channels')
140 log.error('Incorrect permissions for requested channels')
139 raise HTTPForbidden()
141 raise HTTPForbidden()
140 payload = {'conn_id': json_body.get('conn_id', ''),
142 payload = {'conn_id': json_body.get('conn_id', ''),
141 'channels': channels,
143 'channels': channels,
142 'channel_configs': {},
144 'channel_configs': {},
143 'info': {
145 'info': {
144 'exclude_channels': ['broadcast']}
146 'exclude_channels': ['broadcast']}
145 }
147 }
146 filtered_channels = [chan for chan in channels if chan != 'broadcast']
148 filtered_channels = [chan for chan in channels if chan != 'broadcast']
147 for channel in filtered_channels:
149 for channel in filtered_channels:
148 payload['channel_configs'][channel] = {
150 payload['channel_configs'][channel] = {
149 'notify_presence': True,
151 'notify_presence': True,
150 'history_size': 100,
152 'history_size': 100,
151 'store_history': True,
153 'store_history': True,
152 'broadcast_presence_with_user_lists': True
154 'broadcast_presence_with_user_lists': True
153 }
155 }
154 try:
156 try:
155 connect_result = channelstream_request(
157 connect_result = channelstream_request(
156 self.channelstream_config, payload, '/subscribe')
158 self.channelstream_config, payload, '/subscribe')
157 except ChannelstreamConnectionException:
159 except ChannelstreamConnectionException:
158 log.exception('Channelstream service is down')
160 log.exception('Channelstream service is down')
159 return HTTPBadGateway()
161 return HTTPBadGateway()
160 # include_channel_info will limit history only to new channel
162 # include_channel_info will limit history only to new channel
161 # to not overwrite histories on other channels in client
163 # to not overwrite histories on other channels in client
162 connect_result['channels_info'] = parse_channels_info(
164 connect_result['channels_info'] = parse_channels_info(
163 connect_result['channels_info'],
165 connect_result['channels_info'],
164 include_channel_info=filtered_channels)
166 include_channel_info=filtered_channels)
165 update_history_from_logs(self.channelstream_config,
167 update_history_from_logs(self.channelstream_config,
166 filtered_channels, connect_result)
168 filtered_channels, connect_result)
167 return connect_result
169 return connect_result
@@ -1,59 +1,59 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import os
21 import os
22 import logging
22 import logging
23
23
24 from pyramid.view import view_config
24 from pyramid.view import view_config
25 from pyramid.renderers import render_to_response
25 from pyramid.renderers import render_to_response
26 from rhodecode.apps._base import BaseAppView
26 from rhodecode.apps._base import BaseAppView
27
27
28 log = logging.getLogger(__name__)
28 log = logging.getLogger(__name__)
29
29
30
30
31 class DebugStyleView(BaseAppView):
31 class DebugStyleView(BaseAppView):
32 def load_default_context(self):
32 def load_default_context(self):
33 c = self._get_local_tmpl_context()
33 c = self._get_local_tmpl_context()
34 self._register_global_c(c)
34
35 return c
35 return c
36
36
37 @view_config(
37 @view_config(
38 route_name='debug_style_home', request_method='GET',
38 route_name='debug_style_home', request_method='GET',
39 renderer=None)
39 renderer=None)
40 def index(self):
40 def index(self):
41 c = self.load_default_context()
41 c = self.load_default_context()
42 c.active = 'index'
42 c.active = 'index'
43
43
44 return render_to_response(
44 return render_to_response(
45 'debug_style/index.html', self._get_template_context(c),
45 'debug_style/index.html', self._get_template_context(c),
46 request=self.request)
46 request=self.request)
47
47
48 @view_config(
48 @view_config(
49 route_name='debug_style_template', request_method='GET',
49 route_name='debug_style_template', request_method='GET',
50 renderer=None)
50 renderer=None)
51 def template(self):
51 def template(self):
52 t_path = self.request.matchdict['t_path']
52 t_path = self.request.matchdict['t_path']
53 c = self.load_default_context()
53 c = self.load_default_context()
54 c.active = os.path.splitext(t_path)[0]
54 c.active = os.path.splitext(t_path)[0]
55 c.came_from = ''
55 c.came_from = ''
56
56
57 return render_to_response(
57 return render_to_response(
58 'debug_style/' + t_path, self._get_template_context(c),
58 'debug_style/' + t_path, self._get_template_context(c),
59 request=self.request) No newline at end of file
59 request=self.request)
@@ -1,413 +1,413 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2013-2017 RhodeCode GmbH
3 # Copyright (C) 2013-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import time
21 import time
22 import logging
22 import logging
23
23
24 import formencode
24 import formencode
25 import formencode.htmlfill
25 import formencode.htmlfill
26 import peppercorn
26 import peppercorn
27
27
28 from pyramid.httpexceptions import HTTPNotFound, HTTPFound
28 from pyramid.httpexceptions import HTTPNotFound, HTTPFound
29 from pyramid.view import view_config
29 from pyramid.view import view_config
30 from pyramid.renderers import render
30 from pyramid.renderers import render
31 from pyramid.response import Response
31 from pyramid.response import Response
32
32
33 from rhodecode.apps._base import BaseAppView
33 from rhodecode.apps._base import BaseAppView
34 from rhodecode.lib import helpers as h
34 from rhodecode.lib import helpers as h
35 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
35 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
36 from rhodecode.lib.utils2 import time_to_datetime
36 from rhodecode.lib.utils2 import time_to_datetime
37 from rhodecode.lib.ext_json import json
37 from rhodecode.lib.ext_json import json
38 from rhodecode.lib.vcs.exceptions import VCSError, NodeNotChangedError
38 from rhodecode.lib.vcs.exceptions import VCSError, NodeNotChangedError
39 from rhodecode.model.gist import GistModel
39 from rhodecode.model.gist import GistModel
40 from rhodecode.model.meta import Session
40 from rhodecode.model.meta import Session
41 from rhodecode.model.db import Gist, User, or_
41 from rhodecode.model.db import Gist, User, or_
42 from rhodecode.model import validation_schema
42 from rhodecode.model import validation_schema
43 from rhodecode.model.validation_schema.schemas import gist_schema
43 from rhodecode.model.validation_schema.schemas import gist_schema
44
44
45
45
46 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
47
47
48
48
49 class GistView(BaseAppView):
49 class GistView(BaseAppView):
50
50
51 def load_default_context(self):
51 def load_default_context(self):
52 _ = self.request.translate
52 _ = self.request.translate
53 c = self._get_local_tmpl_context()
53 c = self._get_local_tmpl_context()
54 c.user = c.auth_user.get_instance()
54 c.user = c.auth_user.get_instance()
55
55
56 c.lifetime_values = [
56 c.lifetime_values = [
57 (-1, _('forever')),
57 (-1, _('forever')),
58 (5, _('5 minutes')),
58 (5, _('5 minutes')),
59 (60, _('1 hour')),
59 (60, _('1 hour')),
60 (60 * 24, _('1 day')),
60 (60 * 24, _('1 day')),
61 (60 * 24 * 30, _('1 month')),
61 (60 * 24 * 30, _('1 month')),
62 ]
62 ]
63
63
64 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
64 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
65 c.acl_options = [
65 c.acl_options = [
66 (Gist.ACL_LEVEL_PRIVATE, _("Requires registered account")),
66 (Gist.ACL_LEVEL_PRIVATE, _("Requires registered account")),
67 (Gist.ACL_LEVEL_PUBLIC, _("Can be accessed by anonymous users"))
67 (Gist.ACL_LEVEL_PUBLIC, _("Can be accessed by anonymous users"))
68 ]
68 ]
69
69
70 self._register_global_c(c)
70
71 return c
71 return c
72
72
73 @LoginRequired()
73 @LoginRequired()
74 @view_config(
74 @view_config(
75 route_name='gists_show', request_method='GET',
75 route_name='gists_show', request_method='GET',
76 renderer='rhodecode:templates/admin/gists/index.mako')
76 renderer='rhodecode:templates/admin/gists/index.mako')
77 def gist_show_all(self):
77 def gist_show_all(self):
78 c = self.load_default_context()
78 c = self.load_default_context()
79
79
80 not_default_user = self._rhodecode_user.username != User.DEFAULT_USER
80 not_default_user = self._rhodecode_user.username != User.DEFAULT_USER
81 c.show_private = self.request.GET.get('private') and not_default_user
81 c.show_private = self.request.GET.get('private') and not_default_user
82 c.show_public = self.request.GET.get('public') and not_default_user
82 c.show_public = self.request.GET.get('public') and not_default_user
83 c.show_all = self.request.GET.get('all') and self._rhodecode_user.admin
83 c.show_all = self.request.GET.get('all') and self._rhodecode_user.admin
84
84
85 gists = _gists = Gist().query()\
85 gists = _gists = Gist().query()\
86 .filter(or_(Gist.gist_expires == -1, Gist.gist_expires >= time.time()))\
86 .filter(or_(Gist.gist_expires == -1, Gist.gist_expires >= time.time()))\
87 .order_by(Gist.created_on.desc())
87 .order_by(Gist.created_on.desc())
88
88
89 c.active = 'public'
89 c.active = 'public'
90 # MY private
90 # MY private
91 if c.show_private and not c.show_public:
91 if c.show_private and not c.show_public:
92 gists = _gists.filter(Gist.gist_type == Gist.GIST_PRIVATE)\
92 gists = _gists.filter(Gist.gist_type == Gist.GIST_PRIVATE)\
93 .filter(Gist.gist_owner == self._rhodecode_user.user_id)
93 .filter(Gist.gist_owner == self._rhodecode_user.user_id)
94 c.active = 'my_private'
94 c.active = 'my_private'
95 # MY public
95 # MY public
96 elif c.show_public and not c.show_private:
96 elif c.show_public and not c.show_private:
97 gists = _gists.filter(Gist.gist_type == Gist.GIST_PUBLIC)\
97 gists = _gists.filter(Gist.gist_type == Gist.GIST_PUBLIC)\
98 .filter(Gist.gist_owner == self._rhodecode_user.user_id)
98 .filter(Gist.gist_owner == self._rhodecode_user.user_id)
99 c.active = 'my_public'
99 c.active = 'my_public'
100 # MY public+private
100 # MY public+private
101 elif c.show_private and c.show_public:
101 elif c.show_private and c.show_public:
102 gists = _gists.filter(or_(Gist.gist_type == Gist.GIST_PUBLIC,
102 gists = _gists.filter(or_(Gist.gist_type == Gist.GIST_PUBLIC,
103 Gist.gist_type == Gist.GIST_PRIVATE))\
103 Gist.gist_type == Gist.GIST_PRIVATE))\
104 .filter(Gist.gist_owner == self._rhodecode_user.user_id)
104 .filter(Gist.gist_owner == self._rhodecode_user.user_id)
105 c.active = 'my_all'
105 c.active = 'my_all'
106 # Show all by super-admin
106 # Show all by super-admin
107 elif c.show_all:
107 elif c.show_all:
108 c.active = 'all'
108 c.active = 'all'
109 gists = _gists
109 gists = _gists
110
110
111 # default show ALL public gists
111 # default show ALL public gists
112 if not c.show_public and not c.show_private and not c.show_all:
112 if not c.show_public and not c.show_private and not c.show_all:
113 gists = _gists.filter(Gist.gist_type == Gist.GIST_PUBLIC)
113 gists = _gists.filter(Gist.gist_type == Gist.GIST_PUBLIC)
114 c.active = 'public'
114 c.active = 'public'
115
115
116 _render = self.request.get_partial_renderer(
116 _render = self.request.get_partial_renderer(
117 'rhodecode:templates/data_table/_dt_elements.mako')
117 'rhodecode:templates/data_table/_dt_elements.mako')
118
118
119 data = []
119 data = []
120
120
121 for gist in gists:
121 for gist in gists:
122 data.append({
122 data.append({
123 'created_on': _render('gist_created', gist.created_on),
123 'created_on': _render('gist_created', gist.created_on),
124 'created_on_raw': gist.created_on,
124 'created_on_raw': gist.created_on,
125 'type': _render('gist_type', gist.gist_type),
125 'type': _render('gist_type', gist.gist_type),
126 'access_id': _render('gist_access_id', gist.gist_access_id, gist.owner.full_contact),
126 'access_id': _render('gist_access_id', gist.gist_access_id, gist.owner.full_contact),
127 'author': _render('gist_author', gist.owner.full_contact, gist.created_on, gist.gist_expires),
127 'author': _render('gist_author', gist.owner.full_contact, gist.created_on, gist.gist_expires),
128 'author_raw': h.escape(gist.owner.full_contact),
128 'author_raw': h.escape(gist.owner.full_contact),
129 'expires': _render('gist_expires', gist.gist_expires),
129 'expires': _render('gist_expires', gist.gist_expires),
130 'description': _render('gist_description', gist.gist_description)
130 'description': _render('gist_description', gist.gist_description)
131 })
131 })
132 c.data = json.dumps(data)
132 c.data = json.dumps(data)
133
133
134 return self._get_template_context(c)
134 return self._get_template_context(c)
135
135
136 @LoginRequired()
136 @LoginRequired()
137 @NotAnonymous()
137 @NotAnonymous()
138 @view_config(
138 @view_config(
139 route_name='gists_new', request_method='GET',
139 route_name='gists_new', request_method='GET',
140 renderer='rhodecode:templates/admin/gists/new.mako')
140 renderer='rhodecode:templates/admin/gists/new.mako')
141 def gist_new(self):
141 def gist_new(self):
142 c = self.load_default_context()
142 c = self.load_default_context()
143 return self._get_template_context(c)
143 return self._get_template_context(c)
144
144
145 @LoginRequired()
145 @LoginRequired()
146 @NotAnonymous()
146 @NotAnonymous()
147 @CSRFRequired()
147 @CSRFRequired()
148 @view_config(
148 @view_config(
149 route_name='gists_create', request_method='POST',
149 route_name='gists_create', request_method='POST',
150 renderer='rhodecode:templates/admin/gists/new.mako')
150 renderer='rhodecode:templates/admin/gists/new.mako')
151 def gist_create(self):
151 def gist_create(self):
152 _ = self.request.translate
152 _ = self.request.translate
153 c = self.load_default_context()
153 c = self.load_default_context()
154
154
155 data = dict(self.request.POST)
155 data = dict(self.request.POST)
156 data['filename'] = data.get('filename') or Gist.DEFAULT_FILENAME
156 data['filename'] = data.get('filename') or Gist.DEFAULT_FILENAME
157 data['nodes'] = [{
157 data['nodes'] = [{
158 'filename': data['filename'],
158 'filename': data['filename'],
159 'content': data.get('content'),
159 'content': data.get('content'),
160 'mimetype': data.get('mimetype') # None is autodetect
160 'mimetype': data.get('mimetype') # None is autodetect
161 }]
161 }]
162
162
163 data['gist_type'] = (
163 data['gist_type'] = (
164 Gist.GIST_PUBLIC if data.get('public') else Gist.GIST_PRIVATE)
164 Gist.GIST_PUBLIC if data.get('public') else Gist.GIST_PRIVATE)
165 data['gist_acl_level'] = (
165 data['gist_acl_level'] = (
166 data.get('gist_acl_level') or Gist.ACL_LEVEL_PRIVATE)
166 data.get('gist_acl_level') or Gist.ACL_LEVEL_PRIVATE)
167
167
168 schema = gist_schema.GistSchema().bind(
168 schema = gist_schema.GistSchema().bind(
169 lifetime_options=[x[0] for x in c.lifetime_values])
169 lifetime_options=[x[0] for x in c.lifetime_values])
170
170
171 try:
171 try:
172
172
173 schema_data = schema.deserialize(data)
173 schema_data = schema.deserialize(data)
174 # convert to safer format with just KEYs so we sure no duplicates
174 # convert to safer format with just KEYs so we sure no duplicates
175 schema_data['nodes'] = gist_schema.sequence_to_nodes(
175 schema_data['nodes'] = gist_schema.sequence_to_nodes(
176 schema_data['nodes'])
176 schema_data['nodes'])
177
177
178 gist = GistModel().create(
178 gist = GistModel().create(
179 gist_id=schema_data['gistid'], # custom access id not real ID
179 gist_id=schema_data['gistid'], # custom access id not real ID
180 description=schema_data['description'],
180 description=schema_data['description'],
181 owner=self._rhodecode_user.user_id,
181 owner=self._rhodecode_user.user_id,
182 gist_mapping=schema_data['nodes'],
182 gist_mapping=schema_data['nodes'],
183 gist_type=schema_data['gist_type'],
183 gist_type=schema_data['gist_type'],
184 lifetime=schema_data['lifetime'],
184 lifetime=schema_data['lifetime'],
185 gist_acl_level=schema_data['gist_acl_level']
185 gist_acl_level=schema_data['gist_acl_level']
186 )
186 )
187 Session().commit()
187 Session().commit()
188 new_gist_id = gist.gist_access_id
188 new_gist_id = gist.gist_access_id
189 except validation_schema.Invalid as errors:
189 except validation_schema.Invalid as errors:
190 defaults = data
190 defaults = data
191 errors = errors.asdict()
191 errors = errors.asdict()
192
192
193 if 'nodes.0.content' in errors:
193 if 'nodes.0.content' in errors:
194 errors['content'] = errors['nodes.0.content']
194 errors['content'] = errors['nodes.0.content']
195 del errors['nodes.0.content']
195 del errors['nodes.0.content']
196 if 'nodes.0.filename' in errors:
196 if 'nodes.0.filename' in errors:
197 errors['filename'] = errors['nodes.0.filename']
197 errors['filename'] = errors['nodes.0.filename']
198 del errors['nodes.0.filename']
198 del errors['nodes.0.filename']
199
199
200 data = render('rhodecode:templates/admin/gists/new.mako',
200 data = render('rhodecode:templates/admin/gists/new.mako',
201 self._get_template_context(c), self.request)
201 self._get_template_context(c), self.request)
202 html = formencode.htmlfill.render(
202 html = formencode.htmlfill.render(
203 data,
203 data,
204 defaults=defaults,
204 defaults=defaults,
205 errors=errors,
205 errors=errors,
206 prefix_error=False,
206 prefix_error=False,
207 encoding="UTF-8",
207 encoding="UTF-8",
208 force_defaults=False
208 force_defaults=False
209 )
209 )
210 return Response(html)
210 return Response(html)
211
211
212 except Exception:
212 except Exception:
213 log.exception("Exception while trying to create a gist")
213 log.exception("Exception while trying to create a gist")
214 h.flash(_('Error occurred during gist creation'), category='error')
214 h.flash(_('Error occurred during gist creation'), category='error')
215 raise HTTPFound(h.route_url('gists_new'))
215 raise HTTPFound(h.route_url('gists_new'))
216 raise HTTPFound(h.route_url('gist_show', gist_id=new_gist_id))
216 raise HTTPFound(h.route_url('gist_show', gist_id=new_gist_id))
217
217
218 @LoginRequired()
218 @LoginRequired()
219 @NotAnonymous()
219 @NotAnonymous()
220 @CSRFRequired()
220 @CSRFRequired()
221 @view_config(
221 @view_config(
222 route_name='gist_delete', request_method='POST')
222 route_name='gist_delete', request_method='POST')
223 def gist_delete(self):
223 def gist_delete(self):
224 _ = self.request.translate
224 _ = self.request.translate
225 gist_id = self.request.matchdict['gist_id']
225 gist_id = self.request.matchdict['gist_id']
226
226
227 c = self.load_default_context()
227 c = self.load_default_context()
228 c.gist = Gist.get_or_404(gist_id)
228 c.gist = Gist.get_or_404(gist_id)
229
229
230 owner = c.gist.gist_owner == self._rhodecode_user.user_id
230 owner = c.gist.gist_owner == self._rhodecode_user.user_id
231 if not (h.HasPermissionAny('hg.admin')() or owner):
231 if not (h.HasPermissionAny('hg.admin')() or owner):
232 log.warning('Deletion of Gist was forbidden '
232 log.warning('Deletion of Gist was forbidden '
233 'by unauthorized user: `%s`', self._rhodecode_user)
233 'by unauthorized user: `%s`', self._rhodecode_user)
234 raise HTTPNotFound()
234 raise HTTPNotFound()
235
235
236 GistModel().delete(c.gist)
236 GistModel().delete(c.gist)
237 Session().commit()
237 Session().commit()
238 h.flash(_('Deleted gist %s') % c.gist.gist_access_id, category='success')
238 h.flash(_('Deleted gist %s') % c.gist.gist_access_id, category='success')
239
239
240 raise HTTPFound(h.route_url('gists_show'))
240 raise HTTPFound(h.route_url('gists_show'))
241
241
242 def _get_gist(self, gist_id):
242 def _get_gist(self, gist_id):
243
243
244 gist = Gist.get_or_404(gist_id)
244 gist = Gist.get_or_404(gist_id)
245
245
246 # Check if this gist is expired
246 # Check if this gist is expired
247 if gist.gist_expires != -1:
247 if gist.gist_expires != -1:
248 if time.time() > gist.gist_expires:
248 if time.time() > gist.gist_expires:
249 log.error(
249 log.error(
250 'Gist expired at %s', time_to_datetime(gist.gist_expires))
250 'Gist expired at %s', time_to_datetime(gist.gist_expires))
251 raise HTTPNotFound()
251 raise HTTPNotFound()
252
252
253 # check if this gist requires a login
253 # check if this gist requires a login
254 is_default_user = self._rhodecode_user.username == User.DEFAULT_USER
254 is_default_user = self._rhodecode_user.username == User.DEFAULT_USER
255 if gist.acl_level == Gist.ACL_LEVEL_PRIVATE and is_default_user:
255 if gist.acl_level == Gist.ACL_LEVEL_PRIVATE and is_default_user:
256 log.error("Anonymous user %s tried to access protected gist `%s`",
256 log.error("Anonymous user %s tried to access protected gist `%s`",
257 self._rhodecode_user, gist_id)
257 self._rhodecode_user, gist_id)
258 raise HTTPNotFound()
258 raise HTTPNotFound()
259 return gist
259 return gist
260
260
261 @LoginRequired()
261 @LoginRequired()
262 @view_config(
262 @view_config(
263 route_name='gist_show', request_method='GET',
263 route_name='gist_show', request_method='GET',
264 renderer='rhodecode:templates/admin/gists/show.mako')
264 renderer='rhodecode:templates/admin/gists/show.mako')
265 @view_config(
265 @view_config(
266 route_name='gist_show_rev', request_method='GET',
266 route_name='gist_show_rev', request_method='GET',
267 renderer='rhodecode:templates/admin/gists/show.mako')
267 renderer='rhodecode:templates/admin/gists/show.mako')
268 @view_config(
268 @view_config(
269 route_name='gist_show_formatted', request_method='GET',
269 route_name='gist_show_formatted', request_method='GET',
270 renderer=None)
270 renderer=None)
271 @view_config(
271 @view_config(
272 route_name='gist_show_formatted_path', request_method='GET',
272 route_name='gist_show_formatted_path', request_method='GET',
273 renderer=None)
273 renderer=None)
274 def gist_show(self):
274 def gist_show(self):
275 gist_id = self.request.matchdict['gist_id']
275 gist_id = self.request.matchdict['gist_id']
276
276
277 # TODO(marcink): expose those via matching dict
277 # TODO(marcink): expose those via matching dict
278 revision = self.request.matchdict.get('revision', 'tip')
278 revision = self.request.matchdict.get('revision', 'tip')
279 f_path = self.request.matchdict.get('f_path', None)
279 f_path = self.request.matchdict.get('f_path', None)
280 return_format = self.request.matchdict.get('format')
280 return_format = self.request.matchdict.get('format')
281
281
282 c = self.load_default_context()
282 c = self.load_default_context()
283 c.gist = self._get_gist(gist_id)
283 c.gist = self._get_gist(gist_id)
284 c.render = not self.request.GET.get('no-render', False)
284 c.render = not self.request.GET.get('no-render', False)
285
285
286 try:
286 try:
287 c.file_last_commit, c.files = GistModel().get_gist_files(
287 c.file_last_commit, c.files = GistModel().get_gist_files(
288 gist_id, revision=revision)
288 gist_id, revision=revision)
289 except VCSError:
289 except VCSError:
290 log.exception("Exception in gist show")
290 log.exception("Exception in gist show")
291 raise HTTPNotFound()
291 raise HTTPNotFound()
292
292
293 if return_format == 'raw':
293 if return_format == 'raw':
294 content = '\n\n'.join([f.content for f in c.files
294 content = '\n\n'.join([f.content for f in c.files
295 if (f_path is None or f.path == f_path)])
295 if (f_path is None or f.path == f_path)])
296 response = Response(content)
296 response = Response(content)
297 response.content_type = 'text/plain'
297 response.content_type = 'text/plain'
298 return response
298 return response
299
299
300 return self._get_template_context(c)
300 return self._get_template_context(c)
301
301
302 @LoginRequired()
302 @LoginRequired()
303 @NotAnonymous()
303 @NotAnonymous()
304 @view_config(
304 @view_config(
305 route_name='gist_edit', request_method='GET',
305 route_name='gist_edit', request_method='GET',
306 renderer='rhodecode:templates/admin/gists/edit.mako')
306 renderer='rhodecode:templates/admin/gists/edit.mako')
307 def gist_edit(self):
307 def gist_edit(self):
308 _ = self.request.translate
308 _ = self.request.translate
309 gist_id = self.request.matchdict['gist_id']
309 gist_id = self.request.matchdict['gist_id']
310 c = self.load_default_context()
310 c = self.load_default_context()
311 c.gist = self._get_gist(gist_id)
311 c.gist = self._get_gist(gist_id)
312
312
313 owner = c.gist.gist_owner == self._rhodecode_user.user_id
313 owner = c.gist.gist_owner == self._rhodecode_user.user_id
314 if not (h.HasPermissionAny('hg.admin')() or owner):
314 if not (h.HasPermissionAny('hg.admin')() or owner):
315 raise HTTPNotFound()
315 raise HTTPNotFound()
316
316
317 try:
317 try:
318 c.file_last_commit, c.files = GistModel().get_gist_files(gist_id)
318 c.file_last_commit, c.files = GistModel().get_gist_files(gist_id)
319 except VCSError:
319 except VCSError:
320 log.exception("Exception in gist edit")
320 log.exception("Exception in gist edit")
321 raise HTTPNotFound()
321 raise HTTPNotFound()
322
322
323 if c.gist.gist_expires == -1:
323 if c.gist.gist_expires == -1:
324 expiry = _('never')
324 expiry = _('never')
325 else:
325 else:
326 # this cannot use timeago, since it's used in select2 as a value
326 # this cannot use timeago, since it's used in select2 as a value
327 expiry = h.age(h.time_to_datetime(c.gist.gist_expires))
327 expiry = h.age(h.time_to_datetime(c.gist.gist_expires))
328
328
329 c.lifetime_values.append(
329 c.lifetime_values.append(
330 (0, _('%(expiry)s - current value') % {'expiry': _(expiry)})
330 (0, _('%(expiry)s - current value') % {'expiry': _(expiry)})
331 )
331 )
332
332
333 return self._get_template_context(c)
333 return self._get_template_context(c)
334
334
335 @LoginRequired()
335 @LoginRequired()
336 @NotAnonymous()
336 @NotAnonymous()
337 @CSRFRequired()
337 @CSRFRequired()
338 @view_config(
338 @view_config(
339 route_name='gist_update', request_method='POST',
339 route_name='gist_update', request_method='POST',
340 renderer='rhodecode:templates/admin/gists/edit.mako')
340 renderer='rhodecode:templates/admin/gists/edit.mako')
341 def gist_update(self):
341 def gist_update(self):
342 _ = self.request.translate
342 _ = self.request.translate
343 gist_id = self.request.matchdict['gist_id']
343 gist_id = self.request.matchdict['gist_id']
344 c = self.load_default_context()
344 c = self.load_default_context()
345 c.gist = self._get_gist(gist_id)
345 c.gist = self._get_gist(gist_id)
346
346
347 owner = c.gist.gist_owner == self._rhodecode_user.user_id
347 owner = c.gist.gist_owner == self._rhodecode_user.user_id
348 if not (h.HasPermissionAny('hg.admin')() or owner):
348 if not (h.HasPermissionAny('hg.admin')() or owner):
349 raise HTTPNotFound()
349 raise HTTPNotFound()
350
350
351 data = peppercorn.parse(self.request.POST.items())
351 data = peppercorn.parse(self.request.POST.items())
352
352
353 schema = gist_schema.GistSchema()
353 schema = gist_schema.GistSchema()
354 schema = schema.bind(
354 schema = schema.bind(
355 # '0' is special value to leave lifetime untouched
355 # '0' is special value to leave lifetime untouched
356 lifetime_options=[x[0] for x in c.lifetime_values] + [0],
356 lifetime_options=[x[0] for x in c.lifetime_values] + [0],
357 )
357 )
358
358
359 try:
359 try:
360 schema_data = schema.deserialize(data)
360 schema_data = schema.deserialize(data)
361 # convert to safer format with just KEYs so we sure no duplicates
361 # convert to safer format with just KEYs so we sure no duplicates
362 schema_data['nodes'] = gist_schema.sequence_to_nodes(
362 schema_data['nodes'] = gist_schema.sequence_to_nodes(
363 schema_data['nodes'])
363 schema_data['nodes'])
364
364
365 GistModel().update(
365 GistModel().update(
366 gist=c.gist,
366 gist=c.gist,
367 description=schema_data['description'],
367 description=schema_data['description'],
368 owner=c.gist.owner,
368 owner=c.gist.owner,
369 gist_mapping=schema_data['nodes'],
369 gist_mapping=schema_data['nodes'],
370 lifetime=schema_data['lifetime'],
370 lifetime=schema_data['lifetime'],
371 gist_acl_level=schema_data['gist_acl_level']
371 gist_acl_level=schema_data['gist_acl_level']
372 )
372 )
373
373
374 Session().commit()
374 Session().commit()
375 h.flash(_('Successfully updated gist content'), category='success')
375 h.flash(_('Successfully updated gist content'), category='success')
376 except NodeNotChangedError:
376 except NodeNotChangedError:
377 # raised if nothing was changed in repo itself. We anyway then
377 # raised if nothing was changed in repo itself. We anyway then
378 # store only DB stuff for gist
378 # store only DB stuff for gist
379 Session().commit()
379 Session().commit()
380 h.flash(_('Successfully updated gist data'), category='success')
380 h.flash(_('Successfully updated gist data'), category='success')
381 except validation_schema.Invalid as errors:
381 except validation_schema.Invalid as errors:
382 errors = h.escape(errors.asdict())
382 errors = h.escape(errors.asdict())
383 h.flash(_('Error occurred during update of gist {}: {}').format(
383 h.flash(_('Error occurred during update of gist {}: {}').format(
384 gist_id, errors), category='error')
384 gist_id, errors), category='error')
385 except Exception:
385 except Exception:
386 log.exception("Exception in gist edit")
386 log.exception("Exception in gist edit")
387 h.flash(_('Error occurred during update of gist %s') % gist_id,
387 h.flash(_('Error occurred during update of gist %s') % gist_id,
388 category='error')
388 category='error')
389
389
390 raise HTTPFound(h.route_url('gist_show', gist_id=gist_id))
390 raise HTTPFound(h.route_url('gist_show', gist_id=gist_id))
391
391
392 @LoginRequired()
392 @LoginRequired()
393 @NotAnonymous()
393 @NotAnonymous()
394 @view_config(
394 @view_config(
395 route_name='gist_edit_check_revision', request_method='GET',
395 route_name='gist_edit_check_revision', request_method='GET',
396 renderer='json_ext')
396 renderer='json_ext')
397 def gist_edit_check_revision(self):
397 def gist_edit_check_revision(self):
398 _ = self.request.translate
398 _ = self.request.translate
399 gist_id = self.request.matchdict['gist_id']
399 gist_id = self.request.matchdict['gist_id']
400 c = self.load_default_context()
400 c = self.load_default_context()
401 c.gist = self._get_gist(gist_id)
401 c.gist = self._get_gist(gist_id)
402
402
403 last_rev = c.gist.scm_instance().get_commit()
403 last_rev = c.gist.scm_instance().get_commit()
404 success = True
404 success = True
405 revision = self.request.GET.get('revision')
405 revision = self.request.GET.get('revision')
406
406
407 if revision != last_rev.raw_id:
407 if revision != last_rev.raw_id:
408 log.error('Last revision %s is different then submitted %s'
408 log.error('Last revision %s is different then submitted %s'
409 % (revision, last_rev))
409 % (revision, last_rev))
410 # our gist has newer version than we
410 # our gist has newer version than we
411 success = False
411 success = False
412
412
413 return {'success': success}
413 return {'success': success}
@@ -1,151 +1,151 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import json
21 import json
22
22
23 import pytest
23 import pytest
24
24
25 from . import assert_and_get_content
25 from . import assert_and_get_content
26 from rhodecode.tests import TestController, TEST_USER_ADMIN_LOGIN
26 from rhodecode.tests import TestController, TEST_USER_ADMIN_LOGIN
27 from rhodecode.tests.fixture import Fixture
27 from rhodecode.tests.fixture import Fixture
28
28
29 from rhodecode.lib.utils import map_groups
29 from rhodecode.lib.utils import map_groups
30 from rhodecode.model.repo import RepoModel
30 from rhodecode.model.repo import RepoModel
31 from rhodecode.model.repo_group import RepoGroupModel
31 from rhodecode.model.repo_group import RepoGroupModel
32 from rhodecode.model.db import Session, Repository, RepoGroup
32 from rhodecode.model.db import Session, Repository, RepoGroup
33
33
34 fixture = Fixture()
34 fixture = Fixture()
35
35
36
36
37 def route_path(name, params=None, **kwargs):
37 def route_path(name, params=None, **kwargs):
38 import urllib
38 import urllib
39
39
40 base_url = {
40 base_url = {
41 'goto_switcher_data': '/_goto_data',
41 'goto_switcher_data': '/_goto_data',
42 }[name].format(**kwargs)
42 }[name].format(**kwargs)
43
43
44 if params:
44 if params:
45 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
45 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
46 return base_url
46 return base_url
47
47
48
48
49 class TestGotoSwitcherData(TestController):
49 class TestGotoSwitcherData(TestController):
50
50
51 required_repos_with_groups = [
51 required_repos_with_groups = [
52 'abc',
52 'abc',
53 'abc-fork',
53 'abc-fork',
54 'forks/abcd',
54 'forks/abcd',
55 'abcd',
55 'abcd',
56 'abcde',
56 'abcde',
57 'a/abc',
57 'a/abc',
58 'aa/abc',
58 'aa/abc',
59 'aaa/abc',
59 'aaa/abc',
60 'aaaa/abc',
60 'aaaa/abc',
61 'repos_abc/aaa/abc',
61 'repos_abc/aaa/abc',
62 'abc_repos/abc',
62 'abc_repos/abc',
63 'abc_repos/abcd',
63 'abc_repos/abcd',
64 'xxx/xyz',
64 'xxx/xyz',
65 'forked-abc/a/abc'
65 'forked-abc/a/abc'
66 ]
66 ]
67
67
68 @pytest.fixture(autouse=True, scope='class')
68 @pytest.fixture(autouse=True, scope='class')
69 def prepare(self, request, pylonsapp):
69 def prepare(self, request, baseapp):
70 for repo_and_group in self.required_repos_with_groups:
70 for repo_and_group in self.required_repos_with_groups:
71 # create structure of groups and return the last group
71 # create structure of groups and return the last group
72
72
73 repo_group = map_groups(repo_and_group)
73 repo_group = map_groups(repo_and_group)
74
74
75 RepoModel()._create_repo(
75 RepoModel()._create_repo(
76 repo_and_group, 'hg', 'test-ac', TEST_USER_ADMIN_LOGIN,
76 repo_and_group, 'hg', 'test-ac', TEST_USER_ADMIN_LOGIN,
77 repo_group=getattr(repo_group, 'group_id', None))
77 repo_group=getattr(repo_group, 'group_id', None))
78
78
79 Session().commit()
79 Session().commit()
80
80
81 request.addfinalizer(self.cleanup)
81 request.addfinalizer(self.cleanup)
82
82
83 def cleanup(self):
83 def cleanup(self):
84 # first delete all repos
84 # first delete all repos
85 for repo_and_groups in self.required_repos_with_groups:
85 for repo_and_groups in self.required_repos_with_groups:
86 repo = Repository.get_by_repo_name(repo_and_groups)
86 repo = Repository.get_by_repo_name(repo_and_groups)
87 if repo:
87 if repo:
88 RepoModel().delete(repo)
88 RepoModel().delete(repo)
89 Session().commit()
89 Session().commit()
90
90
91 # then delete all empty groups
91 # then delete all empty groups
92 for repo_and_groups in self.required_repos_with_groups:
92 for repo_and_groups in self.required_repos_with_groups:
93 if '/' in repo_and_groups:
93 if '/' in repo_and_groups:
94 r_group = repo_and_groups.rsplit('/', 1)[0]
94 r_group = repo_and_groups.rsplit('/', 1)[0]
95 repo_group = RepoGroup.get_by_group_name(r_group)
95 repo_group = RepoGroup.get_by_group_name(r_group)
96 if not repo_group:
96 if not repo_group:
97 continue
97 continue
98 parents = repo_group.parents
98 parents = repo_group.parents
99 RepoGroupModel().delete(repo_group, force_delete=True)
99 RepoGroupModel().delete(repo_group, force_delete=True)
100 Session().commit()
100 Session().commit()
101
101
102 for el in reversed(parents):
102 for el in reversed(parents):
103 RepoGroupModel().delete(el, force_delete=True)
103 RepoGroupModel().delete(el, force_delete=True)
104 Session().commit()
104 Session().commit()
105
105
106 def test_returns_list_of_repos_and_groups(self, xhr_header):
106 def test_returns_list_of_repos_and_groups(self, xhr_header):
107 self.log_user()
107 self.log_user()
108
108
109 response = self.app.get(
109 response = self.app.get(
110 route_path('goto_switcher_data'),
110 route_path('goto_switcher_data'),
111 extra_environ=xhr_header, status=200)
111 extra_environ=xhr_header, status=200)
112 result = json.loads(response.body)['results']
112 result = json.loads(response.body)['results']
113
113
114 repos, groups, commits = assert_and_get_content(result)
114 repos, groups, commits = assert_and_get_content(result)
115
115
116 assert len(repos) == len(Repository.get_all())
116 assert len(repos) == len(Repository.get_all())
117 assert len(groups) == len(RepoGroup.get_all())
117 assert len(groups) == len(RepoGroup.get_all())
118 assert len(commits) == 0
118 assert len(commits) == 0
119
119
120 def test_returns_list_of_repos_and_groups_filtered(self, xhr_header):
120 def test_returns_list_of_repos_and_groups_filtered(self, xhr_header):
121 self.log_user()
121 self.log_user()
122
122
123 response = self.app.get(
123 response = self.app.get(
124 route_path('goto_switcher_data'),
124 route_path('goto_switcher_data'),
125 params={'query': 'abc'},
125 params={'query': 'abc'},
126 extra_environ=xhr_header, status=200)
126 extra_environ=xhr_header, status=200)
127 result = json.loads(response.body)['results']
127 result = json.loads(response.body)['results']
128
128
129 repos, groups, commits = assert_and_get_content(result)
129 repos, groups, commits = assert_and_get_content(result)
130
130
131 assert len(repos) == 13
131 assert len(repos) == 13
132 assert len(groups) == 5
132 assert len(groups) == 5
133 assert len(commits) == 0
133 assert len(commits) == 0
134
134
135 def test_returns_list_of_properly_sorted_and_filtered(self, xhr_header):
135 def test_returns_list_of_properly_sorted_and_filtered(self, xhr_header):
136 self.log_user()
136 self.log_user()
137
137
138 response = self.app.get(
138 response = self.app.get(
139 route_path('goto_switcher_data'),
139 route_path('goto_switcher_data'),
140 params={'query': 'abc'},
140 params={'query': 'abc'},
141 extra_environ=xhr_header, status=200)
141 extra_environ=xhr_header, status=200)
142 result = json.loads(response.body)['results']
142 result = json.loads(response.body)['results']
143
143
144 repos, groups, commits = assert_and_get_content(result)
144 repos, groups, commits = assert_and_get_content(result)
145
145
146 test_repos = [x['text'] for x in repos[:4]]
146 test_repos = [x['text'] for x in repos[:4]]
147 assert ['abc', 'abcd', 'a/abc', 'abcde'] == test_repos
147 assert ['abc', 'abcd', 'a/abc', 'abcde'] == test_repos
148
148
149 test_groups = [x['text'] for x in groups[:4]]
149 test_groups = [x['text'] for x in groups[:4]]
150 assert ['abc_repos', 'repos_abc',
150 assert ['abc_repos', 'repos_abc',
151 'forked-abc', 'forked-abc/a'] == test_groups
151 'forked-abc', 'forked-abc/a'] == test_groups
@@ -1,325 +1,325 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import re
21 import re
22 import logging
22 import logging
23
23
24 from pyramid.view import view_config
24 from pyramid.view import view_config
25
25
26 from rhodecode.apps._base import BaseAppView
26 from rhodecode.apps._base import BaseAppView
27 from rhodecode.lib import helpers as h
27 from rhodecode.lib import helpers as h
28 from rhodecode.lib.auth import (
28 from rhodecode.lib.auth import (
29 LoginRequired, NotAnonymous, HasRepoGroupPermissionAnyDecorator)
29 LoginRequired, NotAnonymous, HasRepoGroupPermissionAnyDecorator)
30 from rhodecode.lib.index import searcher_from_config
30 from rhodecode.lib.index import searcher_from_config
31 from rhodecode.lib.utils2 import safe_unicode, str2bool
31 from rhodecode.lib.utils2 import safe_unicode, str2bool
32 from rhodecode.lib.ext_json import json
32 from rhodecode.lib.ext_json import json
33 from rhodecode.model.db import (
33 from rhodecode.model.db import (
34 func, or_, in_filter_generator, Repository, RepoGroup)
34 func, or_, in_filter_generator, Repository, RepoGroup)
35 from rhodecode.model.repo import RepoModel
35 from rhodecode.model.repo import RepoModel
36 from rhodecode.model.repo_group import RepoGroupModel
36 from rhodecode.model.repo_group import RepoGroupModel
37 from rhodecode.model.scm import RepoGroupList, RepoList
37 from rhodecode.model.scm import RepoGroupList, RepoList
38 from rhodecode.model.user import UserModel
38 from rhodecode.model.user import UserModel
39 from rhodecode.model.user_group import UserGroupModel
39 from rhodecode.model.user_group import UserGroupModel
40
40
41 log = logging.getLogger(__name__)
41 log = logging.getLogger(__name__)
42
42
43
43
44 class HomeView(BaseAppView):
44 class HomeView(BaseAppView):
45
45
46 def load_default_context(self):
46 def load_default_context(self):
47 c = self._get_local_tmpl_context()
47 c = self._get_local_tmpl_context()
48 c.user = c.auth_user.get_instance()
48 c.user = c.auth_user.get_instance()
49 self._register_global_c(c)
49
50 return c
50 return c
51
51
52 @LoginRequired()
52 @LoginRequired()
53 @view_config(
53 @view_config(
54 route_name='user_autocomplete_data', request_method='GET',
54 route_name='user_autocomplete_data', request_method='GET',
55 renderer='json_ext', xhr=True)
55 renderer='json_ext', xhr=True)
56 def user_autocomplete_data(self):
56 def user_autocomplete_data(self):
57 self.load_default_context()
57 self.load_default_context()
58 query = self.request.GET.get('query')
58 query = self.request.GET.get('query')
59 active = str2bool(self.request.GET.get('active') or True)
59 active = str2bool(self.request.GET.get('active') or True)
60 include_groups = str2bool(self.request.GET.get('user_groups'))
60 include_groups = str2bool(self.request.GET.get('user_groups'))
61 expand_groups = str2bool(self.request.GET.get('user_groups_expand'))
61 expand_groups = str2bool(self.request.GET.get('user_groups_expand'))
62 skip_default_user = str2bool(self.request.GET.get('skip_default_user'))
62 skip_default_user = str2bool(self.request.GET.get('skip_default_user'))
63
63
64 log.debug('generating user list, query:%s, active:%s, with_groups:%s',
64 log.debug('generating user list, query:%s, active:%s, with_groups:%s',
65 query, active, include_groups)
65 query, active, include_groups)
66
66
67 _users = UserModel().get_users(
67 _users = UserModel().get_users(
68 name_contains=query, only_active=active)
68 name_contains=query, only_active=active)
69
69
70 def maybe_skip_default_user(usr):
70 def maybe_skip_default_user(usr):
71 if skip_default_user and usr['username'] == UserModel.cls.DEFAULT_USER:
71 if skip_default_user and usr['username'] == UserModel.cls.DEFAULT_USER:
72 return False
72 return False
73 return True
73 return True
74 _users = filter(maybe_skip_default_user, _users)
74 _users = filter(maybe_skip_default_user, _users)
75
75
76 if include_groups:
76 if include_groups:
77 # extend with user groups
77 # extend with user groups
78 _user_groups = UserGroupModel().get_user_groups(
78 _user_groups = UserGroupModel().get_user_groups(
79 name_contains=query, only_active=active,
79 name_contains=query, only_active=active,
80 expand_groups=expand_groups)
80 expand_groups=expand_groups)
81 _users = _users + _user_groups
81 _users = _users + _user_groups
82
82
83 return {'suggestions': _users}
83 return {'suggestions': _users}
84
84
85 @LoginRequired()
85 @LoginRequired()
86 @NotAnonymous()
86 @NotAnonymous()
87 @view_config(
87 @view_config(
88 route_name='user_group_autocomplete_data', request_method='GET',
88 route_name='user_group_autocomplete_data', request_method='GET',
89 renderer='json_ext', xhr=True)
89 renderer='json_ext', xhr=True)
90 def user_group_autocomplete_data(self):
90 def user_group_autocomplete_data(self):
91 self.load_default_context()
91 self.load_default_context()
92 query = self.request.GET.get('query')
92 query = self.request.GET.get('query')
93 active = str2bool(self.request.GET.get('active') or True)
93 active = str2bool(self.request.GET.get('active') or True)
94 expand_groups = str2bool(self.request.GET.get('user_groups_expand'))
94 expand_groups = str2bool(self.request.GET.get('user_groups_expand'))
95
95
96 log.debug('generating user group list, query:%s, active:%s',
96 log.debug('generating user group list, query:%s, active:%s',
97 query, active)
97 query, active)
98
98
99 _user_groups = UserGroupModel().get_user_groups(
99 _user_groups = UserGroupModel().get_user_groups(
100 name_contains=query, only_active=active,
100 name_contains=query, only_active=active,
101 expand_groups=expand_groups)
101 expand_groups=expand_groups)
102 _user_groups = _user_groups
102 _user_groups = _user_groups
103
103
104 return {'suggestions': _user_groups}
104 return {'suggestions': _user_groups}
105
105
106 def _get_repo_list(self, name_contains=None, repo_type=None, limit=20):
106 def _get_repo_list(self, name_contains=None, repo_type=None, limit=20):
107 allowed_ids = self._rhodecode_user.repo_acl_ids(
107 allowed_ids = self._rhodecode_user.repo_acl_ids(
108 ['repository.read', 'repository.write', 'repository.admin'],
108 ['repository.read', 'repository.write', 'repository.admin'],
109 cache=False, name_filter=name_contains) or [-1]
109 cache=False, name_filter=name_contains) or [-1]
110
110
111 query = Repository.query()\
111 query = Repository.query()\
112 .order_by(func.length(Repository.repo_name))\
112 .order_by(func.length(Repository.repo_name))\
113 .order_by(Repository.repo_name)\
113 .order_by(Repository.repo_name)\
114 .filter(or_(
114 .filter(or_(
115 # generate multiple IN to fix limitation problems
115 # generate multiple IN to fix limitation problems
116 *in_filter_generator(Repository.repo_id, allowed_ids)
116 *in_filter_generator(Repository.repo_id, allowed_ids)
117 ))
117 ))
118
118
119 if repo_type:
119 if repo_type:
120 query = query.filter(Repository.repo_type == repo_type)
120 query = query.filter(Repository.repo_type == repo_type)
121
121
122 if name_contains:
122 if name_contains:
123 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
123 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
124 query = query.filter(
124 query = query.filter(
125 Repository.repo_name.ilike(ilike_expression))
125 Repository.repo_name.ilike(ilike_expression))
126 query = query.limit(limit)
126 query = query.limit(limit)
127
127
128 acl_repo_iter = query
128 acl_repo_iter = query
129
129
130 return [
130 return [
131 {
131 {
132 'id': obj.repo_name,
132 'id': obj.repo_name,
133 'text': obj.repo_name,
133 'text': obj.repo_name,
134 'type': 'repo',
134 'type': 'repo',
135 'obj': {'repo_type': obj.repo_type, 'private': obj.private,
135 'obj': {'repo_type': obj.repo_type, 'private': obj.private,
136 'repo_id': obj.repo_id},
136 'repo_id': obj.repo_id},
137 'url': h.route_path('repo_summary', repo_name=obj.repo_name)
137 'url': h.route_path('repo_summary', repo_name=obj.repo_name)
138 }
138 }
139 for obj in acl_repo_iter]
139 for obj in acl_repo_iter]
140
140
141 def _get_repo_group_list(self, name_contains=None, limit=20):
141 def _get_repo_group_list(self, name_contains=None, limit=20):
142 allowed_ids = self._rhodecode_user.repo_group_acl_ids(
142 allowed_ids = self._rhodecode_user.repo_group_acl_ids(
143 ['group.read', 'group.write', 'group.admin'],
143 ['group.read', 'group.write', 'group.admin'],
144 cache=False, name_filter=name_contains) or [-1]
144 cache=False, name_filter=name_contains) or [-1]
145
145
146 query = RepoGroup.query()\
146 query = RepoGroup.query()\
147 .order_by(func.length(RepoGroup.group_name))\
147 .order_by(func.length(RepoGroup.group_name))\
148 .order_by(RepoGroup.group_name) \
148 .order_by(RepoGroup.group_name) \
149 .filter(or_(
149 .filter(or_(
150 # generate multiple IN to fix limitation problems
150 # generate multiple IN to fix limitation problems
151 *in_filter_generator(RepoGroup.group_id, allowed_ids)
151 *in_filter_generator(RepoGroup.group_id, allowed_ids)
152 ))
152 ))
153
153
154 if name_contains:
154 if name_contains:
155 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
155 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
156 query = query.filter(
156 query = query.filter(
157 RepoGroup.group_name.ilike(ilike_expression))
157 RepoGroup.group_name.ilike(ilike_expression))
158 query = query.limit(limit)
158 query = query.limit(limit)
159
159
160 acl_repo_iter = query
160 acl_repo_iter = query
161
161
162 return [
162 return [
163 {
163 {
164 'id': obj.group_name,
164 'id': obj.group_name,
165 'text': obj.group_name,
165 'text': obj.group_name,
166 'type': 'group',
166 'type': 'group',
167 'obj': {},
167 'obj': {},
168 'url': h.route_path(
168 'url': h.route_path(
169 'repo_group_home', repo_group_name=obj.group_name)
169 'repo_group_home', repo_group_name=obj.group_name)
170 }
170 }
171 for obj in acl_repo_iter]
171 for obj in acl_repo_iter]
172
172
173 def _get_hash_commit_list(self, auth_user, query=None):
173 def _get_hash_commit_list(self, auth_user, query=None):
174 if not query or len(query) < 3:
174 if not query or len(query) < 3:
175 return []
175 return []
176
176
177 commit_hashes = re.compile('(?:commit:)([0-9a-f]{2,40})').findall(query)
177 commit_hashes = re.compile('(?:commit:)([0-9a-f]{2,40})').findall(query)
178
178
179 if len(commit_hashes) != 1:
179 if len(commit_hashes) != 1:
180 return []
180 return []
181
181
182 commit_hash_prefix = commit_hashes[0]
182 commit_hash_prefix = commit_hashes[0]
183
183
184 searcher = searcher_from_config(self.request.registry.settings)
184 searcher = searcher_from_config(self.request.registry.settings)
185 result = searcher.search(
185 result = searcher.search(
186 'commit_id:%s*' % commit_hash_prefix, 'commit', auth_user,
186 'commit_id:%s*' % commit_hash_prefix, 'commit', auth_user,
187 raise_on_exc=False)
187 raise_on_exc=False)
188
188
189 return [
189 return [
190 {
190 {
191 'id': entry['commit_id'],
191 'id': entry['commit_id'],
192 'text': entry['commit_id'],
192 'text': entry['commit_id'],
193 'type': 'commit',
193 'type': 'commit',
194 'obj': {'repo': entry['repository']},
194 'obj': {'repo': entry['repository']},
195 'url': h.route_path(
195 'url': h.route_path(
196 'repo_commit',
196 'repo_commit',
197 repo_name=entry['repository'], commit_id=entry['commit_id'])
197 repo_name=entry['repository'], commit_id=entry['commit_id'])
198 }
198 }
199 for entry in result['results']]
199 for entry in result['results']]
200
200
201 @LoginRequired()
201 @LoginRequired()
202 @view_config(
202 @view_config(
203 route_name='repo_list_data', request_method='GET',
203 route_name='repo_list_data', request_method='GET',
204 renderer='json_ext', xhr=True)
204 renderer='json_ext', xhr=True)
205 def repo_list_data(self):
205 def repo_list_data(self):
206 _ = self.request.translate
206 _ = self.request.translate
207 self.load_default_context()
207 self.load_default_context()
208
208
209 query = self.request.GET.get('query')
209 query = self.request.GET.get('query')
210 repo_type = self.request.GET.get('repo_type')
210 repo_type = self.request.GET.get('repo_type')
211 log.debug('generating repo list, query:%s, repo_type:%s',
211 log.debug('generating repo list, query:%s, repo_type:%s',
212 query, repo_type)
212 query, repo_type)
213
213
214 res = []
214 res = []
215 repos = self._get_repo_list(query, repo_type=repo_type)
215 repos = self._get_repo_list(query, repo_type=repo_type)
216 if repos:
216 if repos:
217 res.append({
217 res.append({
218 'text': _('Repositories'),
218 'text': _('Repositories'),
219 'children': repos
219 'children': repos
220 })
220 })
221
221
222 data = {
222 data = {
223 'more': False,
223 'more': False,
224 'results': res
224 'results': res
225 }
225 }
226 return data
226 return data
227
227
228 @LoginRequired()
228 @LoginRequired()
229 @view_config(
229 @view_config(
230 route_name='goto_switcher_data', request_method='GET',
230 route_name='goto_switcher_data', request_method='GET',
231 renderer='json_ext', xhr=True)
231 renderer='json_ext', xhr=True)
232 def goto_switcher_data(self):
232 def goto_switcher_data(self):
233 c = self.load_default_context()
233 c = self.load_default_context()
234
234
235 _ = self.request.translate
235 _ = self.request.translate
236
236
237 query = self.request.GET.get('query')
237 query = self.request.GET.get('query')
238 log.debug('generating goto switcher list, query %s', query)
238 log.debug('generating goto switcher list, query %s', query)
239
239
240 res = []
240 res = []
241 repo_groups = self._get_repo_group_list(query)
241 repo_groups = self._get_repo_group_list(query)
242 if repo_groups:
242 if repo_groups:
243 res.append({
243 res.append({
244 'text': _('Groups'),
244 'text': _('Groups'),
245 'children': repo_groups
245 'children': repo_groups
246 })
246 })
247
247
248 repos = self._get_repo_list(query)
248 repos = self._get_repo_list(query)
249 if repos:
249 if repos:
250 res.append({
250 res.append({
251 'text': _('Repositories'),
251 'text': _('Repositories'),
252 'children': repos
252 'children': repos
253 })
253 })
254
254
255 commits = self._get_hash_commit_list(c.auth_user, query)
255 commits = self._get_hash_commit_list(c.auth_user, query)
256 if commits:
256 if commits:
257 unique_repos = {}
257 unique_repos = {}
258 for commit in commits:
258 for commit in commits:
259 unique_repos.setdefault(commit['obj']['repo'], []
259 unique_repos.setdefault(commit['obj']['repo'], []
260 ).append(commit)
260 ).append(commit)
261
261
262 for repo in unique_repos:
262 for repo in unique_repos:
263 res.append({
263 res.append({
264 'text': _('Commits in %(repo)s') % {'repo': repo},
264 'text': _('Commits in %(repo)s') % {'repo': repo},
265 'children': unique_repos[repo]
265 'children': unique_repos[repo]
266 })
266 })
267
267
268 data = {
268 data = {
269 'more': False,
269 'more': False,
270 'results': res
270 'results': res
271 }
271 }
272 return data
272 return data
273
273
274 def _get_groups_and_repos(self, repo_group_id=None):
274 def _get_groups_and_repos(self, repo_group_id=None):
275 # repo groups groups
275 # repo groups groups
276 repo_group_list = RepoGroup.get_all_repo_groups(group_id=repo_group_id)
276 repo_group_list = RepoGroup.get_all_repo_groups(group_id=repo_group_id)
277 _perms = ['group.read', 'group.write', 'group.admin']
277 _perms = ['group.read', 'group.write', 'group.admin']
278 repo_group_list_acl = RepoGroupList(repo_group_list, perm_set=_perms)
278 repo_group_list_acl = RepoGroupList(repo_group_list, perm_set=_perms)
279 repo_group_data = RepoGroupModel().get_repo_groups_as_dict(
279 repo_group_data = RepoGroupModel().get_repo_groups_as_dict(
280 repo_group_list=repo_group_list_acl, admin=False)
280 repo_group_list=repo_group_list_acl, admin=False)
281
281
282 # repositories
282 # repositories
283 repo_list = Repository.get_all_repos(group_id=repo_group_id)
283 repo_list = Repository.get_all_repos(group_id=repo_group_id)
284 _perms = ['repository.read', 'repository.write', 'repository.admin']
284 _perms = ['repository.read', 'repository.write', 'repository.admin']
285 repo_list_acl = RepoList(repo_list, perm_set=_perms)
285 repo_list_acl = RepoList(repo_list, perm_set=_perms)
286 repo_data = RepoModel().get_repos_as_dict(
286 repo_data = RepoModel().get_repos_as_dict(
287 repo_list=repo_list_acl, admin=False)
287 repo_list=repo_list_acl, admin=False)
288
288
289 return repo_data, repo_group_data
289 return repo_data, repo_group_data
290
290
291 @LoginRequired()
291 @LoginRequired()
292 @view_config(
292 @view_config(
293 route_name='home', request_method='GET',
293 route_name='home', request_method='GET',
294 renderer='rhodecode:templates/index.mako')
294 renderer='rhodecode:templates/index.mako')
295 def main_page(self):
295 def main_page(self):
296 c = self.load_default_context()
296 c = self.load_default_context()
297 c.repo_group = None
297 c.repo_group = None
298
298
299 repo_data, repo_group_data = self._get_groups_and_repos()
299 repo_data, repo_group_data = self._get_groups_and_repos()
300 # json used to render the grids
300 # json used to render the grids
301 c.repos_data = json.dumps(repo_data)
301 c.repos_data = json.dumps(repo_data)
302 c.repo_groups_data = json.dumps(repo_group_data)
302 c.repo_groups_data = json.dumps(repo_group_data)
303
303
304 return self._get_template_context(c)
304 return self._get_template_context(c)
305
305
306 @LoginRequired()
306 @LoginRequired()
307 @HasRepoGroupPermissionAnyDecorator(
307 @HasRepoGroupPermissionAnyDecorator(
308 'group.read', 'group.write', 'group.admin')
308 'group.read', 'group.write', 'group.admin')
309 @view_config(
309 @view_config(
310 route_name='repo_group_home', request_method='GET',
310 route_name='repo_group_home', request_method='GET',
311 renderer='rhodecode:templates/index_repo_group.mako')
311 renderer='rhodecode:templates/index_repo_group.mako')
312 @view_config(
312 @view_config(
313 route_name='repo_group_home_slash', request_method='GET',
313 route_name='repo_group_home_slash', request_method='GET',
314 renderer='rhodecode:templates/index_repo_group.mako')
314 renderer='rhodecode:templates/index_repo_group.mako')
315 def repo_group_main_page(self):
315 def repo_group_main_page(self):
316 c = self.load_default_context()
316 c = self.load_default_context()
317 c.repo_group = self.request.db_repo_group
317 c.repo_group = self.request.db_repo_group
318 repo_data, repo_group_data = self._get_groups_and_repos(
318 repo_data, repo_group_data = self._get_groups_and_repos(
319 c.repo_group.group_id)
319 c.repo_group.group_id)
320
320
321 # json used to render the grids
321 # json used to render the grids
322 c.repos_data = json.dumps(repo_data)
322 c.repos_data = json.dumps(repo_data)
323 c.repo_groups_data = json.dumps(repo_group_data)
323 c.repo_groups_data = json.dumps(repo_group_data)
324
324
325 return self._get_template_context(c)
325 return self._get_template_context(c)
@@ -1,384 +1,386 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import logging
22 import logging
23 import itertools
23 import itertools
24
24
25 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
25 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
26
26
27 from pyramid.view import view_config
27 from pyramid.view import view_config
28 from pyramid.httpexceptions import HTTPBadRequest
28 from pyramid.httpexceptions import HTTPBadRequest
29 from pyramid.response import Response
29 from pyramid.response import Response
30 from pyramid.renderers import render
30 from pyramid.renderers import render
31
31
32 from rhodecode.apps._base import BaseAppView
32 from rhodecode.apps._base import BaseAppView
33 from rhodecode.model.db import (
33 from rhodecode.model.db import (
34 or_, joinedload, UserLog, UserFollowing, User, UserApiKeys)
34 or_, joinedload, UserLog, UserFollowing, User, UserApiKeys)
35 from rhodecode.model.meta import Session
35 from rhodecode.model.meta import Session
36 import rhodecode.lib.helpers as h
36 import rhodecode.lib.helpers as h
37 from rhodecode.lib.helpers import Page
37 from rhodecode.lib.helpers import Page
38 from rhodecode.lib.user_log_filter import user_log_filter
38 from rhodecode.lib.user_log_filter import user_log_filter
39 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
39 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
40 from rhodecode.lib.utils2 import safe_int, AttributeDict, md5_safe
40 from rhodecode.lib.utils2 import safe_int, AttributeDict, md5_safe
41 from rhodecode.model.scm import ScmModel
41 from rhodecode.model.scm import ScmModel
42
42
43 log = logging.getLogger(__name__)
43 log = logging.getLogger(__name__)
44
44
45
45
46 class JournalView(BaseAppView):
46 class JournalView(BaseAppView):
47
47
48 def load_default_context(self):
48 def load_default_context(self):
49 c = self._get_local_tmpl_context(include_app_defaults=True)
49 c = self._get_local_tmpl_context(include_app_defaults=True)
50 self._register_global_c(c)
50
51 self._load_defaults(c.rhodecode_name)
51 self._load_defaults(c.rhodecode_name)
52
52
53 # TODO(marcink): what is this, why we need a global register ?
53 # TODO(marcink): what is this, why we need a global register ?
54 c.search_term = self.request.GET.get('filter') or ''
54 c.search_term = self.request.GET.get('filter') or ''
55 return c
55 return c
56
56
57 def _get_config(self, rhodecode_name):
57 def _get_config(self, rhodecode_name):
58 import rhodecode
58 import rhodecode
59 config = rhodecode.CONFIG
59 config = rhodecode.CONFIG
60
60
61 return {
61 return {
62 'language': 'en-us',
62 'language': 'en-us',
63 'feed_ttl': '5', # TTL of feed,
63 'feed_ttl': '5', # TTL of feed,
64 'feed_items_per_page':
64 'feed_items_per_page':
65 safe_int(config.get('rss_items_per_page', 20)),
65 safe_int(config.get('rss_items_per_page', 20)),
66 'rhodecode_name': rhodecode_name
66 'rhodecode_name': rhodecode_name
67 }
67 }
68
68
69 def _load_defaults(self, rhodecode_name):
69 def _load_defaults(self, rhodecode_name):
70 config = self._get_config(rhodecode_name)
70 config = self._get_config(rhodecode_name)
71 # common values for feeds
71 # common values for feeds
72 self.language = config["language"]
72 self.language = config["language"]
73 self.ttl = config["feed_ttl"]
73 self.ttl = config["feed_ttl"]
74 self.feed_items_per_page = config['feed_items_per_page']
74 self.feed_items_per_page = config['feed_items_per_page']
75 self.rhodecode_name = config['rhodecode_name']
75 self.rhodecode_name = config['rhodecode_name']
76
76
77 def _get_daily_aggregate(self, journal):
77 def _get_daily_aggregate(self, journal):
78 groups = []
78 groups = []
79 for k, g in itertools.groupby(journal, lambda x: x.action_as_day):
79 for k, g in itertools.groupby(journal, lambda x: x.action_as_day):
80 user_group = []
80 user_group = []
81 # groupby username if it's a present value, else
81 # groupby username if it's a present value, else
82 # fallback to journal username
82 # fallback to journal username
83 for _, g2 in itertools.groupby(
83 for _, g2 in itertools.groupby(
84 list(g), lambda x: x.user.username if x.user else x.username):
84 list(g), lambda x: x.user.username if x.user else x.username):
85 l = list(g2)
85 l = list(g2)
86 user_group.append((l[0].user, l))
86 user_group.append((l[0].user, l))
87
87
88 groups.append((k, user_group,))
88 groups.append((k, user_group,))
89
89
90 return groups
90 return groups
91
91
92 def _get_journal_data(self, following_repos, search_term):
92 def _get_journal_data(self, following_repos, search_term):
93 repo_ids = [x.follows_repository.repo_id for x in following_repos
93 repo_ids = [x.follows_repository.repo_id for x in following_repos
94 if x.follows_repository is not None]
94 if x.follows_repository is not None]
95 user_ids = [x.follows_user.user_id for x in following_repos
95 user_ids = [x.follows_user.user_id for x in following_repos
96 if x.follows_user is not None]
96 if x.follows_user is not None]
97
97
98 filtering_criterion = None
98 filtering_criterion = None
99
99
100 if repo_ids and user_ids:
100 if repo_ids and user_ids:
101 filtering_criterion = or_(UserLog.repository_id.in_(repo_ids),
101 filtering_criterion = or_(UserLog.repository_id.in_(repo_ids),
102 UserLog.user_id.in_(user_ids))
102 UserLog.user_id.in_(user_ids))
103 if repo_ids and not user_ids:
103 if repo_ids and not user_ids:
104 filtering_criterion = UserLog.repository_id.in_(repo_ids)
104 filtering_criterion = UserLog.repository_id.in_(repo_ids)
105 if not repo_ids and user_ids:
105 if not repo_ids and user_ids:
106 filtering_criterion = UserLog.user_id.in_(user_ids)
106 filtering_criterion = UserLog.user_id.in_(user_ids)
107 if filtering_criterion is not None:
107 if filtering_criterion is not None:
108 journal = Session().query(UserLog)\
108 journal = Session().query(UserLog)\
109 .options(joinedload(UserLog.user))\
109 .options(joinedload(UserLog.user))\
110 .options(joinedload(UserLog.repository))
110 .options(joinedload(UserLog.repository))
111 # filter
111 # filter
112 try:
112 try:
113 journal = user_log_filter(journal, search_term)
113 journal = user_log_filter(journal, search_term)
114 except Exception:
114 except Exception:
115 # we want this to crash for now
115 # we want this to crash for now
116 raise
116 raise
117 journal = journal.filter(filtering_criterion)\
117 journal = journal.filter(filtering_criterion)\
118 .order_by(UserLog.action_date.desc())
118 .order_by(UserLog.action_date.desc())
119 else:
119 else:
120 journal = []
120 journal = []
121
121
122 return journal
122 return journal
123
123
124 def feed_uid(self, entry_id):
124 def feed_uid(self, entry_id):
125 return '{}:{}'.format('journal', md5_safe(entry_id))
125 return '{}:{}'.format('journal', md5_safe(entry_id))
126
126
127 def _atom_feed(self, repos, search_term, public=True):
127 def _atom_feed(self, repos, search_term, public=True):
128 _ = self.request.translate
128 _ = self.request.translate
129 journal = self._get_journal_data(repos, search_term)
129 journal = self._get_journal_data(repos, search_term)
130 if public:
130 if public:
131 _link = h.route_url('journal_public_atom')
131 _link = h.route_url('journal_public_atom')
132 _desc = '%s %s %s' % (self.rhodecode_name, _('public journal'),
132 _desc = '%s %s %s' % (self.rhodecode_name, _('public journal'),
133 'atom feed')
133 'atom feed')
134 else:
134 else:
135 _link = h.route_url('journal_atom')
135 _link = h.route_url('journal_atom')
136 _desc = '%s %s %s' % (self.rhodecode_name, _('journal'), 'atom feed')
136 _desc = '%s %s %s' % (self.rhodecode_name, _('journal'), 'atom feed')
137
137
138 feed = Atom1Feed(
138 feed = Atom1Feed(
139 title=_desc, link=_link, description=_desc,
139 title=_desc, link=_link, description=_desc,
140 language=self.language, ttl=self.ttl)
140 language=self.language, ttl=self.ttl)
141
141
142 for entry in journal[:self.feed_items_per_page]:
142 for entry in journal[:self.feed_items_per_page]:
143 user = entry.user
143 user = entry.user
144 if user is None:
144 if user is None:
145 # fix deleted users
145 # fix deleted users
146 user = AttributeDict({'short_contact': entry.username,
146 user = AttributeDict({'short_contact': entry.username,
147 'email': '',
147 'email': '',
148 'full_contact': ''})
148 'full_contact': ''})
149 action, action_extra, ico = h.action_parser(entry, feed=True)
149 action, action_extra, ico = h.action_parser(
150 self.request, entry, feed=True)
150 title = "%s - %s %s" % (user.short_contact, action(),
151 title = "%s - %s %s" % (user.short_contact, action(),
151 entry.repository.repo_name)
152 entry.repository.repo_name)
152 desc = action_extra()
153 desc = action_extra()
153 _url = h.route_url('home')
154 _url = h.route_url('home')
154 if entry.repository is not None:
155 if entry.repository is not None:
155 _url = h.route_url('repo_changelog',
156 _url = h.route_url('repo_changelog',
156 repo_name=entry.repository.repo_name)
157 repo_name=entry.repository.repo_name)
157
158
158 feed.add_item(
159 feed.add_item(
159 unique_id=self.feed_uid(entry.user_log_id),
160 unique_id=self.feed_uid(entry.user_log_id),
160 title=title,
161 title=title,
161 pubdate=entry.action_date,
162 pubdate=entry.action_date,
162 link=_url,
163 link=_url,
163 author_email=user.email,
164 author_email=user.email,
164 author_name=user.full_contact,
165 author_name=user.full_contact,
165 description=desc)
166 description=desc)
166
167
167 response = Response(feed.writeString('utf-8'))
168 response = Response(feed.writeString('utf-8'))
168 response.content_type = feed.mime_type
169 response.content_type = feed.mime_type
169 return response
170 return response
170
171
171 def _rss_feed(self, repos, search_term, public=True):
172 def _rss_feed(self, repos, search_term, public=True):
172 _ = self.request.translate
173 _ = self.request.translate
173 journal = self._get_journal_data(repos, search_term)
174 journal = self._get_journal_data(repos, search_term)
174 if public:
175 if public:
175 _link = h.route_url('journal_public_atom')
176 _link = h.route_url('journal_public_atom')
176 _desc = '%s %s %s' % (
177 _desc = '%s %s %s' % (
177 self.rhodecode_name, _('public journal'), 'rss feed')
178 self.rhodecode_name, _('public journal'), 'rss feed')
178 else:
179 else:
179 _link = h.route_url('journal_atom')
180 _link = h.route_url('journal_atom')
180 _desc = '%s %s %s' % (
181 _desc = '%s %s %s' % (
181 self.rhodecode_name, _('journal'), 'rss feed')
182 self.rhodecode_name, _('journal'), 'rss feed')
182
183
183 feed = Rss201rev2Feed(
184 feed = Rss201rev2Feed(
184 title=_desc, link=_link, description=_desc,
185 title=_desc, link=_link, description=_desc,
185 language=self.language, ttl=self.ttl)
186 language=self.language, ttl=self.ttl)
186
187
187 for entry in journal[:self.feed_items_per_page]:
188 for entry in journal[:self.feed_items_per_page]:
188 user = entry.user
189 user = entry.user
189 if user is None:
190 if user is None:
190 # fix deleted users
191 # fix deleted users
191 user = AttributeDict({'short_contact': entry.username,
192 user = AttributeDict({'short_contact': entry.username,
192 'email': '',
193 'email': '',
193 'full_contact': ''})
194 'full_contact': ''})
194 action, action_extra, ico = h.action_parser(entry, feed=True)
195 action, action_extra, ico = h.action_parser(
196 self.request, entry, feed=True)
195 title = "%s - %s %s" % (user.short_contact, action(),
197 title = "%s - %s %s" % (user.short_contact, action(),
196 entry.repository.repo_name)
198 entry.repository.repo_name)
197 desc = action_extra()
199 desc = action_extra()
198 _url = h.route_url('home')
200 _url = h.route_url('home')
199 if entry.repository is not None:
201 if entry.repository is not None:
200 _url = h.route_url('repo_changelog',
202 _url = h.route_url('repo_changelog',
201 repo_name=entry.repository.repo_name)
203 repo_name=entry.repository.repo_name)
202
204
203 feed.add_item(
205 feed.add_item(
204 unique_id=self.feed_uid(entry.user_log_id),
206 unique_id=self.feed_uid(entry.user_log_id),
205 title=title,
207 title=title,
206 pubdate=entry.action_date,
208 pubdate=entry.action_date,
207 link=_url,
209 link=_url,
208 author_email=user.email,
210 author_email=user.email,
209 author_name=user.full_contact,
211 author_name=user.full_contact,
210 description=desc)
212 description=desc)
211
213
212 response = Response(feed.writeString('utf-8'))
214 response = Response(feed.writeString('utf-8'))
213 response.content_type = feed.mime_type
215 response.content_type = feed.mime_type
214 return response
216 return response
215
217
216 @LoginRequired()
218 @LoginRequired()
217 @NotAnonymous()
219 @NotAnonymous()
218 @view_config(
220 @view_config(
219 route_name='journal', request_method='GET',
221 route_name='journal', request_method='GET',
220 renderer=None)
222 renderer=None)
221 def journal(self):
223 def journal(self):
222 c = self.load_default_context()
224 c = self.load_default_context()
223
225
224 p = safe_int(self.request.GET.get('page', 1), 1)
226 p = safe_int(self.request.GET.get('page', 1), 1)
225 c.user = User.get(self._rhodecode_user.user_id)
227 c.user = User.get(self._rhodecode_user.user_id)
226 following = Session().query(UserFollowing)\
228 following = Session().query(UserFollowing)\
227 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
229 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
228 .options(joinedload(UserFollowing.follows_repository))\
230 .options(joinedload(UserFollowing.follows_repository))\
229 .all()
231 .all()
230
232
231 journal = self._get_journal_data(following, c.search_term)
233 journal = self._get_journal_data(following, c.search_term)
232
234
233 def url_generator(**kw):
235 def url_generator(**kw):
234 query_params = {
236 query_params = {
235 'filter': c.search_term
237 'filter': c.search_term
236 }
238 }
237 query_params.update(kw)
239 query_params.update(kw)
238 return self.request.current_route_path(_query=query_params)
240 return self.request.current_route_path(_query=query_params)
239
241
240 c.journal_pager = Page(
242 c.journal_pager = Page(
241 journal, page=p, items_per_page=20, url=url_generator)
243 journal, page=p, items_per_page=20, url=url_generator)
242 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
244 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
243
245
244 c.journal_data = render(
246 c.journal_data = render(
245 'rhodecode:templates/journal/journal_data.mako',
247 'rhodecode:templates/journal/journal_data.mako',
246 self._get_template_context(c), self.request)
248 self._get_template_context(c), self.request)
247
249
248 if self.request.is_xhr:
250 if self.request.is_xhr:
249 return Response(c.journal_data)
251 return Response(c.journal_data)
250
252
251 html = render(
253 html = render(
252 'rhodecode:templates/journal/journal.mako',
254 'rhodecode:templates/journal/journal.mako',
253 self._get_template_context(c), self.request)
255 self._get_template_context(c), self.request)
254 return Response(html)
256 return Response(html)
255
257
256 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
258 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
257 @NotAnonymous()
259 @NotAnonymous()
258 @view_config(
260 @view_config(
259 route_name='journal_atom', request_method='GET',
261 route_name='journal_atom', request_method='GET',
260 renderer=None)
262 renderer=None)
261 def journal_atom(self):
263 def journal_atom(self):
262 """
264 """
263 Produce an atom-1.0 feed via feedgenerator module
265 Produce an atom-1.0 feed via feedgenerator module
264 """
266 """
265 c = self.load_default_context()
267 c = self.load_default_context()
266 following_repos = Session().query(UserFollowing)\
268 following_repos = Session().query(UserFollowing)\
267 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
269 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
268 .options(joinedload(UserFollowing.follows_repository))\
270 .options(joinedload(UserFollowing.follows_repository))\
269 .all()
271 .all()
270 return self._atom_feed(following_repos, c.search_term, public=False)
272 return self._atom_feed(following_repos, c.search_term, public=False)
271
273
272 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
274 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
273 @NotAnonymous()
275 @NotAnonymous()
274 @view_config(
276 @view_config(
275 route_name='journal_rss', request_method='GET',
277 route_name='journal_rss', request_method='GET',
276 renderer=None)
278 renderer=None)
277 def journal_rss(self):
279 def journal_rss(self):
278 """
280 """
279 Produce an rss feed via feedgenerator module
281 Produce an rss feed via feedgenerator module
280 """
282 """
281 c = self.load_default_context()
283 c = self.load_default_context()
282 following_repos = Session().query(UserFollowing)\
284 following_repos = Session().query(UserFollowing)\
283 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
285 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
284 .options(joinedload(UserFollowing.follows_repository))\
286 .options(joinedload(UserFollowing.follows_repository))\
285 .all()
287 .all()
286 return self._rss_feed(following_repos, c.search_term, public=False)
288 return self._rss_feed(following_repos, c.search_term, public=False)
287
289
288 @LoginRequired()
290 @LoginRequired()
289 @NotAnonymous()
291 @NotAnonymous()
290 @CSRFRequired()
292 @CSRFRequired()
291 @view_config(
293 @view_config(
292 route_name='toggle_following', request_method='POST',
294 route_name='toggle_following', request_method='POST',
293 renderer='json_ext')
295 renderer='json_ext')
294 def toggle_following(self):
296 def toggle_following(self):
295 user_id = self.request.POST.get('follows_user_id')
297 user_id = self.request.POST.get('follows_user_id')
296 if user_id:
298 if user_id:
297 try:
299 try:
298 ScmModel().toggle_following_user(
300 ScmModel().toggle_following_user(
299 user_id, self._rhodecode_user.user_id)
301 user_id, self._rhodecode_user.user_id)
300 Session().commit()
302 Session().commit()
301 return 'ok'
303 return 'ok'
302 except Exception:
304 except Exception:
303 raise HTTPBadRequest()
305 raise HTTPBadRequest()
304
306
305 repo_id = self.request.POST.get('follows_repo_id')
307 repo_id = self.request.POST.get('follows_repo_id')
306 if repo_id:
308 if repo_id:
307 try:
309 try:
308 ScmModel().toggle_following_repo(
310 ScmModel().toggle_following_repo(
309 repo_id, self._rhodecode_user.user_id)
311 repo_id, self._rhodecode_user.user_id)
310 Session().commit()
312 Session().commit()
311 return 'ok'
313 return 'ok'
312 except Exception:
314 except Exception:
313 raise HTTPBadRequest()
315 raise HTTPBadRequest()
314
316
315 raise HTTPBadRequest()
317 raise HTTPBadRequest()
316
318
317 @LoginRequired()
319 @LoginRequired()
318 @view_config(
320 @view_config(
319 route_name='journal_public', request_method='GET',
321 route_name='journal_public', request_method='GET',
320 renderer=None)
322 renderer=None)
321 def journal_public(self):
323 def journal_public(self):
322 c = self.load_default_context()
324 c = self.load_default_context()
323 # Return a rendered template
325 # Return a rendered template
324 p = safe_int(self.request.GET.get('page', 1), 1)
326 p = safe_int(self.request.GET.get('page', 1), 1)
325
327
326 c.following = Session().query(UserFollowing)\
328 c.following = Session().query(UserFollowing)\
327 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
329 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
328 .options(joinedload(UserFollowing.follows_repository))\
330 .options(joinedload(UserFollowing.follows_repository))\
329 .all()
331 .all()
330
332
331 journal = self._get_journal_data(c.following, c.search_term)
333 journal = self._get_journal_data(c.following, c.search_term)
332
334
333 def url_generator(**kw):
335 def url_generator(**kw):
334 query_params = {}
336 query_params = {}
335 query_params.update(kw)
337 query_params.update(kw)
336 return self.request.current_route_path(_query=query_params)
338 return self.request.current_route_path(_query=query_params)
337
339
338 c.journal_pager = Page(
340 c.journal_pager = Page(
339 journal, page=p, items_per_page=20, url=url_generator)
341 journal, page=p, items_per_page=20, url=url_generator)
340 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
342 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
341
343
342 c.journal_data = render(
344 c.journal_data = render(
343 'rhodecode:templates/journal/journal_data.mako',
345 'rhodecode:templates/journal/journal_data.mako',
344 self._get_template_context(c), self.request)
346 self._get_template_context(c), self.request)
345
347
346 if self.request.is_xhr:
348 if self.request.is_xhr:
347 return Response(c.journal_data)
349 return Response(c.journal_data)
348
350
349 html = render(
351 html = render(
350 'rhodecode:templates/journal/public_journal.mako',
352 'rhodecode:templates/journal/public_journal.mako',
351 self._get_template_context(c), self.request)
353 self._get_template_context(c), self.request)
352 return Response(html)
354 return Response(html)
353
355
354 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
356 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
355 @view_config(
357 @view_config(
356 route_name='journal_public_atom', request_method='GET',
358 route_name='journal_public_atom', request_method='GET',
357 renderer=None)
359 renderer=None)
358 def journal_public_atom(self):
360 def journal_public_atom(self):
359 """
361 """
360 Produce an atom-1.0 feed via feedgenerator module
362 Produce an atom-1.0 feed via feedgenerator module
361 """
363 """
362 c = self.load_default_context()
364 c = self.load_default_context()
363 following_repos = Session().query(UserFollowing)\
365 following_repos = Session().query(UserFollowing)\
364 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
366 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
365 .options(joinedload(UserFollowing.follows_repository))\
367 .options(joinedload(UserFollowing.follows_repository))\
366 .all()
368 .all()
367
369
368 return self._atom_feed(following_repos, c.search_term)
370 return self._atom_feed(following_repos, c.search_term)
369
371
370 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
372 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
371 @view_config(
373 @view_config(
372 route_name='journal_public_rss', request_method='GET',
374 route_name='journal_public_rss', request_method='GET',
373 renderer=None)
375 renderer=None)
374 def journal_public_rss(self):
376 def journal_public_rss(self):
375 """
377 """
376 Produce an rss2 feed via feedgenerator module
378 Produce an rss2 feed via feedgenerator module
377 """
379 """
378 c = self.load_default_context()
380 c = self.load_default_context()
379 following_repos = Session().query(UserFollowing)\
381 following_repos = Session().query(UserFollowing)\
380 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
382 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
381 .options(joinedload(UserFollowing.follows_repository))\
383 .options(joinedload(UserFollowing.follows_repository))\
382 .all()
384 .all()
383
385
384 return self._rss_feed(following_repos, c.search_term)
386 return self._rss_feed(following_repos, c.search_term)
@@ -1,554 +1,553 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import urlparse
21 import urlparse
22
22
23 import mock
23 import mock
24 import pytest
24 import pytest
25
25
26 from rhodecode.tests import (
26 from rhodecode.tests import (
27 assert_session_flash, HG_REPO, TEST_USER_ADMIN_LOGIN,
27 assert_session_flash, HG_REPO, TEST_USER_ADMIN_LOGIN,
28 no_newline_id_generator)
28 no_newline_id_generator)
29 from rhodecode.tests.fixture import Fixture
29 from rhodecode.tests.fixture import Fixture
30 from rhodecode.lib.auth import check_password
30 from rhodecode.lib.auth import check_password
31 from rhodecode.lib import helpers as h
31 from rhodecode.lib import helpers as h
32 from rhodecode.model.auth_token import AuthTokenModel
32 from rhodecode.model.auth_token import AuthTokenModel
33 from rhodecode.model import validators
34 from rhodecode.model.db import User, Notification, UserApiKeys
33 from rhodecode.model.db import User, Notification, UserApiKeys
35 from rhodecode.model.meta import Session
34 from rhodecode.model.meta import Session
36
35
37 fixture = Fixture()
36 fixture = Fixture()
38
37
39 whitelist_view = ['RepoCommitsView:repo_commit_raw']
38 whitelist_view = ['RepoCommitsView:repo_commit_raw']
40
39
41
40
42 def route_path(name, params=None, **kwargs):
41 def route_path(name, params=None, **kwargs):
43 import urllib
42 import urllib
44 from rhodecode.apps._base import ADMIN_PREFIX
43 from rhodecode.apps._base import ADMIN_PREFIX
45
44
46 base_url = {
45 base_url = {
47 'login': ADMIN_PREFIX + '/login',
46 'login': ADMIN_PREFIX + '/login',
48 'logout': ADMIN_PREFIX + '/logout',
47 'logout': ADMIN_PREFIX + '/logout',
49 'register': ADMIN_PREFIX + '/register',
48 'register': ADMIN_PREFIX + '/register',
50 'reset_password':
49 'reset_password':
51 ADMIN_PREFIX + '/password_reset',
50 ADMIN_PREFIX + '/password_reset',
52 'reset_password_confirmation':
51 'reset_password_confirmation':
53 ADMIN_PREFIX + '/password_reset_confirmation',
52 ADMIN_PREFIX + '/password_reset_confirmation',
54
53
55 'admin_permissions_application':
54 'admin_permissions_application':
56 ADMIN_PREFIX + '/permissions/application',
55 ADMIN_PREFIX + '/permissions/application',
57 'admin_permissions_application_update':
56 'admin_permissions_application_update':
58 ADMIN_PREFIX + '/permissions/application/update',
57 ADMIN_PREFIX + '/permissions/application/update',
59
58
60 'repo_commit_raw': '/{repo_name}/raw-changeset/{commit_id}'
59 'repo_commit_raw': '/{repo_name}/raw-changeset/{commit_id}'
61
60
62 }[name].format(**kwargs)
61 }[name].format(**kwargs)
63
62
64 if params:
63 if params:
65 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
64 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
66 return base_url
65 return base_url
67
66
68
67
69 @pytest.mark.usefixtures('app')
68 @pytest.mark.usefixtures('app')
70 class TestLoginController(object):
69 class TestLoginController(object):
71 destroy_users = set()
70 destroy_users = set()
72
71
73 @classmethod
72 @classmethod
74 def teardown_class(cls):
73 def teardown_class(cls):
75 fixture.destroy_users(cls.destroy_users)
74 fixture.destroy_users(cls.destroy_users)
76
75
77 def teardown_method(self, method):
76 def teardown_method(self, method):
78 for n in Notification.query().all():
77 for n in Notification.query().all():
79 Session().delete(n)
78 Session().delete(n)
80
79
81 Session().commit()
80 Session().commit()
82 assert Notification.query().all() == []
81 assert Notification.query().all() == []
83
82
84 def test_index(self):
83 def test_index(self):
85 response = self.app.get(route_path('login'))
84 response = self.app.get(route_path('login'))
86 assert response.status == '200 OK'
85 assert response.status == '200 OK'
87 # Test response...
86 # Test response...
88
87
89 def test_login_admin_ok(self):
88 def test_login_admin_ok(self):
90 response = self.app.post(route_path('login'),
89 response = self.app.post(route_path('login'),
91 {'username': 'test_admin',
90 {'username': 'test_admin',
92 'password': 'test12'})
91 'password': 'test12'})
93 assert response.status == '302 Found'
92 assert response.status == '302 Found'
94 session = response.get_session_from_response()
93 session = response.get_session_from_response()
95 username = session['rhodecode_user'].get('username')
94 username = session['rhodecode_user'].get('username')
96 assert username == 'test_admin'
95 assert username == 'test_admin'
97 response = response.follow()
96 response = response.follow()
98 response.mustcontain('/%s' % HG_REPO)
97 response.mustcontain('/%s' % HG_REPO)
99
98
100 def test_login_regular_ok(self):
99 def test_login_regular_ok(self):
101 response = self.app.post(route_path('login'),
100 response = self.app.post(route_path('login'),
102 {'username': 'test_regular',
101 {'username': 'test_regular',
103 'password': 'test12'})
102 'password': 'test12'})
104
103
105 assert response.status == '302 Found'
104 assert response.status == '302 Found'
106 session = response.get_session_from_response()
105 session = response.get_session_from_response()
107 username = session['rhodecode_user'].get('username')
106 username = session['rhodecode_user'].get('username')
108 assert username == 'test_regular'
107 assert username == 'test_regular'
109 response = response.follow()
108 response = response.follow()
110 response.mustcontain('/%s' % HG_REPO)
109 response.mustcontain('/%s' % HG_REPO)
111
110
112 def test_login_ok_came_from(self):
111 def test_login_ok_came_from(self):
113 test_came_from = '/_admin/users?branch=stable'
112 test_came_from = '/_admin/users?branch=stable'
114 _url = '{}?came_from={}'.format(route_path('login'), test_came_from)
113 _url = '{}?came_from={}'.format(route_path('login'), test_came_from)
115 response = self.app.post(
114 response = self.app.post(
116 _url, {'username': 'test_admin', 'password': 'test12'})
115 _url, {'username': 'test_admin', 'password': 'test12'})
117 assert response.status == '302 Found'
116 assert response.status == '302 Found'
118 assert 'branch=stable' in response.location
117 assert 'branch=stable' in response.location
119 response = response.follow()
118 response = response.follow()
120
119
121 assert response.status == '200 OK'
120 assert response.status == '200 OK'
122 response.mustcontain('Users administration')
121 response.mustcontain('Users administration')
123
122
124 def test_redirect_to_login_with_get_args(self):
123 def test_redirect_to_login_with_get_args(self):
125 with fixture.anon_access(False):
124 with fixture.anon_access(False):
126 kwargs = {'branch': 'stable'}
125 kwargs = {'branch': 'stable'}
127 response = self.app.get(
126 response = self.app.get(
128 h.route_path('repo_summary', repo_name=HG_REPO, _query=kwargs))
127 h.route_path('repo_summary', repo_name=HG_REPO, _query=kwargs))
129 assert response.status == '302 Found'
128 assert response.status == '302 Found'
130
129
131 response_query = urlparse.parse_qsl(response.location)
130 response_query = urlparse.parse_qsl(response.location)
132 assert 'branch=stable' in response_query[0][1]
131 assert 'branch=stable' in response_query[0][1]
133
132
134 def test_login_form_with_get_args(self):
133 def test_login_form_with_get_args(self):
135 _url = '{}?came_from=/_admin/users,branch=stable'.format(route_path('login'))
134 _url = '{}?came_from=/_admin/users,branch=stable'.format(route_path('login'))
136 response = self.app.get(_url)
135 response = self.app.get(_url)
137 assert 'branch%3Dstable' in response.form.action
136 assert 'branch%3Dstable' in response.form.action
138
137
139 @pytest.mark.parametrize("url_came_from", [
138 @pytest.mark.parametrize("url_came_from", [
140 'data:text/html,<script>window.alert("xss")</script>',
139 'data:text/html,<script>window.alert("xss")</script>',
141 'mailto:test@rhodecode.org',
140 'mailto:test@rhodecode.org',
142 'file:///etc/passwd',
141 'file:///etc/passwd',
143 'ftp://some.ftp.server',
142 'ftp://some.ftp.server',
144 'http://other.domain',
143 'http://other.domain',
145 '/\r\nX-Forwarded-Host: http://example.org',
144 '/\r\nX-Forwarded-Host: http://example.org',
146 ], ids=no_newline_id_generator)
145 ], ids=no_newline_id_generator)
147 def test_login_bad_came_froms(self, url_came_from):
146 def test_login_bad_came_froms(self, url_came_from):
148 _url = '{}?came_from={}'.format(route_path('login'), url_came_from)
147 _url = '{}?came_from={}'.format(route_path('login'), url_came_from)
149 response = self.app.post(
148 response = self.app.post(
150 _url,
149 _url,
151 {'username': 'test_admin', 'password': 'test12'})
150 {'username': 'test_admin', 'password': 'test12'})
152 assert response.status == '302 Found'
151 assert response.status == '302 Found'
153 response = response.follow()
152 response = response.follow()
154 assert response.status == '200 OK'
153 assert response.status == '200 OK'
155 assert response.request.path == '/'
154 assert response.request.path == '/'
156
155
157 def test_login_short_password(self):
156 def test_login_short_password(self):
158 response = self.app.post(route_path('login'),
157 response = self.app.post(route_path('login'),
159 {'username': 'test_admin',
158 {'username': 'test_admin',
160 'password': 'as'})
159 'password': 'as'})
161 assert response.status == '200 OK'
160 assert response.status == '200 OK'
162
161
163 response.mustcontain('Enter 3 characters or more')
162 response.mustcontain('Enter 3 characters or more')
164
163
165 def test_login_wrong_non_ascii_password(self, user_regular):
164 def test_login_wrong_non_ascii_password(self, user_regular):
166 response = self.app.post(
165 response = self.app.post(
167 route_path('login'),
166 route_path('login'),
168 {'username': user_regular.username,
167 {'username': user_regular.username,
169 'password': u'invalid-non-asci\xe4'.encode('utf8')})
168 'password': u'invalid-non-asci\xe4'.encode('utf8')})
170
169
171 response.mustcontain('invalid user name')
170 response.mustcontain('invalid user name')
172 response.mustcontain('invalid password')
171 response.mustcontain('invalid password')
173
172
174 def test_login_with_non_ascii_password(self, user_util):
173 def test_login_with_non_ascii_password(self, user_util):
175 password = u'valid-non-ascii\xe4'
174 password = u'valid-non-ascii\xe4'
176 user = user_util.create_user(password=password)
175 user = user_util.create_user(password=password)
177 response = self.app.post(
176 response = self.app.post(
178 route_path('login'),
177 route_path('login'),
179 {'username': user.username,
178 {'username': user.username,
180 'password': password.encode('utf-8')})
179 'password': password.encode('utf-8')})
181 assert response.status_code == 302
180 assert response.status_code == 302
182
181
183 def test_login_wrong_username_password(self):
182 def test_login_wrong_username_password(self):
184 response = self.app.post(route_path('login'),
183 response = self.app.post(route_path('login'),
185 {'username': 'error',
184 {'username': 'error',
186 'password': 'test12'})
185 'password': 'test12'})
187
186
188 response.mustcontain('invalid user name')
187 response.mustcontain('invalid user name')
189 response.mustcontain('invalid password')
188 response.mustcontain('invalid password')
190
189
191 def test_login_admin_ok_password_migration(self, real_crypto_backend):
190 def test_login_admin_ok_password_migration(self, real_crypto_backend):
192 from rhodecode.lib import auth
191 from rhodecode.lib import auth
193
192
194 # create new user, with sha256 password
193 # create new user, with sha256 password
195 temp_user = 'test_admin_sha256'
194 temp_user = 'test_admin_sha256'
196 user = fixture.create_user(temp_user)
195 user = fixture.create_user(temp_user)
197 user.password = auth._RhodeCodeCryptoSha256().hash_create(
196 user.password = auth._RhodeCodeCryptoSha256().hash_create(
198 b'test123')
197 b'test123')
199 Session().add(user)
198 Session().add(user)
200 Session().commit()
199 Session().commit()
201 self.destroy_users.add(temp_user)
200 self.destroy_users.add(temp_user)
202 response = self.app.post(route_path('login'),
201 response = self.app.post(route_path('login'),
203 {'username': temp_user,
202 {'username': temp_user,
204 'password': 'test123'})
203 'password': 'test123'})
205
204
206 assert response.status == '302 Found'
205 assert response.status == '302 Found'
207 session = response.get_session_from_response()
206 session = response.get_session_from_response()
208 username = session['rhodecode_user'].get('username')
207 username = session['rhodecode_user'].get('username')
209 assert username == temp_user
208 assert username == temp_user
210 response = response.follow()
209 response = response.follow()
211 response.mustcontain('/%s' % HG_REPO)
210 response.mustcontain('/%s' % HG_REPO)
212
211
213 # new password should be bcrypted, after log-in and transfer
212 # new password should be bcrypted, after log-in and transfer
214 user = User.get_by_username(temp_user)
213 user = User.get_by_username(temp_user)
215 assert user.password.startswith('$')
214 assert user.password.startswith('$')
216
215
217 # REGISTRATIONS
216 # REGISTRATIONS
218 def test_register(self):
217 def test_register(self):
219 response = self.app.get(route_path('register'))
218 response = self.app.get(route_path('register'))
220 response.mustcontain('Create an Account')
219 response.mustcontain('Create an Account')
221
220
222 def test_register_err_same_username(self):
221 def test_register_err_same_username(self):
223 uname = 'test_admin'
222 uname = 'test_admin'
224 response = self.app.post(
223 response = self.app.post(
225 route_path('register'),
224 route_path('register'),
226 {
225 {
227 'username': uname,
226 'username': uname,
228 'password': 'test12',
227 'password': 'test12',
229 'password_confirmation': 'test12',
228 'password_confirmation': 'test12',
230 'email': 'goodmail@domain.com',
229 'email': 'goodmail@domain.com',
231 'firstname': 'test',
230 'firstname': 'test',
232 'lastname': 'test'
231 'lastname': 'test'
233 }
232 }
234 )
233 )
235
234
236 assertr = response.assert_response()
235 assertr = response.assert_response()
237 msg = validators.ValidUsername()._messages['username_exists']
236 msg = '???'
238 msg = msg % {'username': uname}
237 msg = msg % {'username': uname}
239 assertr.element_contains('#username+.error-message', msg)
238 assertr.element_contains('#username+.error-message', msg)
240
239
241 def test_register_err_same_email(self):
240 def test_register_err_same_email(self):
242 response = self.app.post(
241 response = self.app.post(
243 route_path('register'),
242 route_path('register'),
244 {
243 {
245 'username': 'test_admin_0',
244 'username': 'test_admin_0',
246 'password': 'test12',
245 'password': 'test12',
247 'password_confirmation': 'test12',
246 'password_confirmation': 'test12',
248 'email': 'test_admin@mail.com',
247 'email': 'test_admin@mail.com',
249 'firstname': 'test',
248 'firstname': 'test',
250 'lastname': 'test'
249 'lastname': 'test'
251 }
250 }
252 )
251 )
253
252
254 assertr = response.assert_response()
253 assertr = response.assert_response()
255 msg = validators.UniqSystemEmail()()._messages['email_taken']
254 msg = '???'
256 assertr.element_contains('#email+.error-message', msg)
255 assertr.element_contains('#email+.error-message', msg)
257
256
258 def test_register_err_same_email_case_sensitive(self):
257 def test_register_err_same_email_case_sensitive(self):
259 response = self.app.post(
258 response = self.app.post(
260 route_path('register'),
259 route_path('register'),
261 {
260 {
262 'username': 'test_admin_1',
261 'username': 'test_admin_1',
263 'password': 'test12',
262 'password': 'test12',
264 'password_confirmation': 'test12',
263 'password_confirmation': 'test12',
265 'email': 'TesT_Admin@mail.COM',
264 'email': 'TesT_Admin@mail.COM',
266 'firstname': 'test',
265 'firstname': 'test',
267 'lastname': 'test'
266 'lastname': 'test'
268 }
267 }
269 )
268 )
270 assertr = response.assert_response()
269 assertr = response.assert_response()
271 msg = validators.UniqSystemEmail()()._messages['email_taken']
270 msg = '???'
272 assertr.element_contains('#email+.error-message', msg)
271 assertr.element_contains('#email+.error-message', msg)
273
272
274 def test_register_err_wrong_data(self):
273 def test_register_err_wrong_data(self):
275 response = self.app.post(
274 response = self.app.post(
276 route_path('register'),
275 route_path('register'),
277 {
276 {
278 'username': 'xs',
277 'username': 'xs',
279 'password': 'test',
278 'password': 'test',
280 'password_confirmation': 'test',
279 'password_confirmation': 'test',
281 'email': 'goodmailm',
280 'email': 'goodmailm',
282 'firstname': 'test',
281 'firstname': 'test',
283 'lastname': 'test'
282 'lastname': 'test'
284 }
283 }
285 )
284 )
286 assert response.status == '200 OK'
285 assert response.status == '200 OK'
287 response.mustcontain('An email address must contain a single @')
286 response.mustcontain('An email address must contain a single @')
288 response.mustcontain('Enter a value 6 characters long or more')
287 response.mustcontain('Enter a value 6 characters long or more')
289
288
290 def test_register_err_username(self):
289 def test_register_err_username(self):
291 response = self.app.post(
290 response = self.app.post(
292 route_path('register'),
291 route_path('register'),
293 {
292 {
294 'username': 'error user',
293 'username': 'error user',
295 'password': 'test12',
294 'password': 'test12',
296 'password_confirmation': 'test12',
295 'password_confirmation': 'test12',
297 'email': 'goodmailm',
296 'email': 'goodmailm',
298 'firstname': 'test',
297 'firstname': 'test',
299 'lastname': 'test'
298 'lastname': 'test'
300 }
299 }
301 )
300 )
302
301
303 response.mustcontain('An email address must contain a single @')
302 response.mustcontain('An email address must contain a single @')
304 response.mustcontain(
303 response.mustcontain(
305 'Username may only contain '
304 'Username may only contain '
306 'alphanumeric characters underscores, '
305 'alphanumeric characters underscores, '
307 'periods or dashes and must begin with '
306 'periods or dashes and must begin with '
308 'alphanumeric character')
307 'alphanumeric character')
309
308
310 def test_register_err_case_sensitive(self):
309 def test_register_err_case_sensitive(self):
311 usr = 'Test_Admin'
310 usr = 'Test_Admin'
312 response = self.app.post(
311 response = self.app.post(
313 route_path('register'),
312 route_path('register'),
314 {
313 {
315 'username': usr,
314 'username': usr,
316 'password': 'test12',
315 'password': 'test12',
317 'password_confirmation': 'test12',
316 'password_confirmation': 'test12',
318 'email': 'goodmailm',
317 'email': 'goodmailm',
319 'firstname': 'test',
318 'firstname': 'test',
320 'lastname': 'test'
319 'lastname': 'test'
321 }
320 }
322 )
321 )
323
322
324 assertr = response.assert_response()
323 assertr = response.assert_response()
325 msg = validators.ValidUsername()._messages['username_exists']
324 msg = '???'
326 msg = msg % {'username': usr}
325 msg = msg % {'username': usr}
327 assertr.element_contains('#username+.error-message', msg)
326 assertr.element_contains('#username+.error-message', msg)
328
327
329 def test_register_special_chars(self):
328 def test_register_special_chars(self):
330 response = self.app.post(
329 response = self.app.post(
331 route_path('register'),
330 route_path('register'),
332 {
331 {
333 'username': 'xxxaxn',
332 'username': 'xxxaxn',
334 'password': 'ąćźżąśśśś',
333 'password': 'ąćźżąśśśś',
335 'password_confirmation': 'ąćźżąśśśś',
334 'password_confirmation': 'ąćźżąśśśś',
336 'email': 'goodmailm@test.plx',
335 'email': 'goodmailm@test.plx',
337 'firstname': 'test',
336 'firstname': 'test',
338 'lastname': 'test'
337 'lastname': 'test'
339 }
338 }
340 )
339 )
341
340
342 msg = validators.ValidPassword()._messages['invalid_password']
341 msg = '???'
343 response.mustcontain(msg)
342 response.mustcontain(msg)
344
343
345 def test_register_password_mismatch(self):
344 def test_register_password_mismatch(self):
346 response = self.app.post(
345 response = self.app.post(
347 route_path('register'),
346 route_path('register'),
348 {
347 {
349 'username': 'xs',
348 'username': 'xs',
350 'password': '123qwe',
349 'password': '123qwe',
351 'password_confirmation': 'qwe123',
350 'password_confirmation': 'qwe123',
352 'email': 'goodmailm@test.plxa',
351 'email': 'goodmailm@test.plxa',
353 'firstname': 'test',
352 'firstname': 'test',
354 'lastname': 'test'
353 'lastname': 'test'
355 }
354 }
356 )
355 )
357 msg = validators.ValidPasswordsMatch()._messages['password_mismatch']
356 msg = '???'
358 response.mustcontain(msg)
357 response.mustcontain(msg)
359
358
360 def test_register_ok(self):
359 def test_register_ok(self):
361 username = 'test_regular4'
360 username = 'test_regular4'
362 password = 'qweqwe'
361 password = 'qweqwe'
363 email = 'marcin@test.com'
362 email = 'marcin@test.com'
364 name = 'testname'
363 name = 'testname'
365 lastname = 'testlastname'
364 lastname = 'testlastname'
366
365
367 response = self.app.post(
366 response = self.app.post(
368 route_path('register'),
367 route_path('register'),
369 {
368 {
370 'username': username,
369 'username': username,
371 'password': password,
370 'password': password,
372 'password_confirmation': password,
371 'password_confirmation': password,
373 'email': email,
372 'email': email,
374 'firstname': name,
373 'firstname': name,
375 'lastname': lastname,
374 'lastname': lastname,
376 'admin': True
375 'admin': True
377 }
376 }
378 ) # This should be overriden
377 ) # This should be overriden
379 assert response.status == '302 Found'
378 assert response.status == '302 Found'
380 assert_session_flash(
379 assert_session_flash(
381 response, 'You have successfully registered with RhodeCode')
380 response, 'You have successfully registered with RhodeCode')
382
381
383 ret = Session().query(User).filter(
382 ret = Session().query(User).filter(
384 User.username == 'test_regular4').one()
383 User.username == 'test_regular4').one()
385 assert ret.username == username
384 assert ret.username == username
386 assert check_password(password, ret.password)
385 assert check_password(password, ret.password)
387 assert ret.email == email
386 assert ret.email == email
388 assert ret.name == name
387 assert ret.name == name
389 assert ret.lastname == lastname
388 assert ret.lastname == lastname
390 assert ret.auth_tokens is not None
389 assert ret.auth_tokens is not None
391 assert not ret.admin
390 assert not ret.admin
392
391
393 def test_forgot_password_wrong_mail(self):
392 def test_forgot_password_wrong_mail(self):
394 bad_email = 'marcin@wrongmail.org'
393 bad_email = 'marcin@wrongmail.org'
395 response = self.app.post(
394 response = self.app.post(
396 route_path('reset_password'), {'email': bad_email, }
395 route_path('reset_password'), {'email': bad_email, }
397 )
396 )
398 assert_session_flash(response,
397 assert_session_flash(response,
399 'If such email exists, a password reset link was sent to it.')
398 'If such email exists, a password reset link was sent to it.')
400
399
401 def test_forgot_password(self, user_util):
400 def test_forgot_password(self, user_util):
402 response = self.app.get(route_path('reset_password'))
401 response = self.app.get(route_path('reset_password'))
403 assert response.status == '200 OK'
402 assert response.status == '200 OK'
404
403
405 user = user_util.create_user()
404 user = user_util.create_user()
406 user_id = user.user_id
405 user_id = user.user_id
407 email = user.email
406 email = user.email
408
407
409 response = self.app.post(route_path('reset_password'), {'email': email, })
408 response = self.app.post(route_path('reset_password'), {'email': email, })
410
409
411 assert_session_flash(response,
410 assert_session_flash(response,
412 'If such email exists, a password reset link was sent to it.')
411 'If such email exists, a password reset link was sent to it.')
413
412
414 # BAD KEY
413 # BAD KEY
415 confirm_url = '{}?key={}'.format(route_path('reset_password_confirmation'), 'badkey')
414 confirm_url = '{}?key={}'.format(route_path('reset_password_confirmation'), 'badkey')
416 response = self.app.get(confirm_url)
415 response = self.app.get(confirm_url)
417 assert response.status == '302 Found'
416 assert response.status == '302 Found'
418 assert response.location.endswith(route_path('reset_password'))
417 assert response.location.endswith(route_path('reset_password'))
419 assert_session_flash(response, 'Given reset token is invalid')
418 assert_session_flash(response, 'Given reset token is invalid')
420
419
421 response.follow() # cleanup flash
420 response.follow() # cleanup flash
422
421
423 # GOOD KEY
422 # GOOD KEY
424 key = UserApiKeys.query()\
423 key = UserApiKeys.query()\
425 .filter(UserApiKeys.user_id == user_id)\
424 .filter(UserApiKeys.user_id == user_id)\
426 .filter(UserApiKeys.role == UserApiKeys.ROLE_PASSWORD_RESET)\
425 .filter(UserApiKeys.role == UserApiKeys.ROLE_PASSWORD_RESET)\
427 .first()
426 .first()
428
427
429 assert key
428 assert key
430
429
431 confirm_url = '{}?key={}'.format(route_path('reset_password_confirmation'), key.api_key)
430 confirm_url = '{}?key={}'.format(route_path('reset_password_confirmation'), key.api_key)
432 response = self.app.get(confirm_url)
431 response = self.app.get(confirm_url)
433 assert response.status == '302 Found'
432 assert response.status == '302 Found'
434 assert response.location.endswith(route_path('login'))
433 assert response.location.endswith(route_path('login'))
435
434
436 assert_session_flash(
435 assert_session_flash(
437 response,
436 response,
438 'Your password reset was successful, '
437 'Your password reset was successful, '
439 'a new password has been sent to your email')
438 'a new password has been sent to your email')
440
439
441 response.follow()
440 response.follow()
442
441
443 def _get_api_whitelist(self, values=None):
442 def _get_api_whitelist(self, values=None):
444 config = {'api_access_controllers_whitelist': values or []}
443 config = {'api_access_controllers_whitelist': values or []}
445 return config
444 return config
446
445
447 @pytest.mark.parametrize("test_name, auth_token", [
446 @pytest.mark.parametrize("test_name, auth_token", [
448 ('none', None),
447 ('none', None),
449 ('empty_string', ''),
448 ('empty_string', ''),
450 ('fake_number', '123456'),
449 ('fake_number', '123456'),
451 ('proper_auth_token', None)
450 ('proper_auth_token', None)
452 ])
451 ])
453 def test_access_not_whitelisted_page_via_auth_token(
452 def test_access_not_whitelisted_page_via_auth_token(
454 self, test_name, auth_token, user_admin):
453 self, test_name, auth_token, user_admin):
455
454
456 whitelist = self._get_api_whitelist([])
455 whitelist = self._get_api_whitelist([])
457 with mock.patch.dict('rhodecode.CONFIG', whitelist):
456 with mock.patch.dict('rhodecode.CONFIG', whitelist):
458 assert [] == whitelist['api_access_controllers_whitelist']
457 assert [] == whitelist['api_access_controllers_whitelist']
459 if test_name == 'proper_auth_token':
458 if test_name == 'proper_auth_token':
460 # use builtin if api_key is None
459 # use builtin if api_key is None
461 auth_token = user_admin.api_key
460 auth_token = user_admin.api_key
462
461
463 with fixture.anon_access(False):
462 with fixture.anon_access(False):
464 self.app.get(
463 self.app.get(
465 route_path('repo_commit_raw',
464 route_path('repo_commit_raw',
466 repo_name=HG_REPO, commit_id='tip',
465 repo_name=HG_REPO, commit_id='tip',
467 params=dict(api_key=auth_token)),
466 params=dict(api_key=auth_token)),
468 status=302)
467 status=302)
469
468
470 @pytest.mark.parametrize("test_name, auth_token, code", [
469 @pytest.mark.parametrize("test_name, auth_token, code", [
471 ('none', None, 302),
470 ('none', None, 302),
472 ('empty_string', '', 302),
471 ('empty_string', '', 302),
473 ('fake_number', '123456', 302),
472 ('fake_number', '123456', 302),
474 ('proper_auth_token', None, 200)
473 ('proper_auth_token', None, 200)
475 ])
474 ])
476 def test_access_whitelisted_page_via_auth_token(
475 def test_access_whitelisted_page_via_auth_token(
477 self, test_name, auth_token, code, user_admin):
476 self, test_name, auth_token, code, user_admin):
478
477
479 whitelist = self._get_api_whitelist(whitelist_view)
478 whitelist = self._get_api_whitelist(whitelist_view)
480
479
481 with mock.patch.dict('rhodecode.CONFIG', whitelist):
480 with mock.patch.dict('rhodecode.CONFIG', whitelist):
482 assert whitelist_view == whitelist['api_access_controllers_whitelist']
481 assert whitelist_view == whitelist['api_access_controllers_whitelist']
483
482
484 if test_name == 'proper_auth_token':
483 if test_name == 'proper_auth_token':
485 auth_token = user_admin.api_key
484 auth_token = user_admin.api_key
486 assert auth_token
485 assert auth_token
487
486
488 with fixture.anon_access(False):
487 with fixture.anon_access(False):
489 self.app.get(
488 self.app.get(
490 route_path('repo_commit_raw',
489 route_path('repo_commit_raw',
491 repo_name=HG_REPO, commit_id='tip',
490 repo_name=HG_REPO, commit_id='tip',
492 params=dict(api_key=auth_token)),
491 params=dict(api_key=auth_token)),
493 status=code)
492 status=code)
494
493
495 @pytest.mark.parametrize("test_name, auth_token, code", [
494 @pytest.mark.parametrize("test_name, auth_token, code", [
496 ('proper_auth_token', None, 200),
495 ('proper_auth_token', None, 200),
497 ('wrong_auth_token', '123456', 302),
496 ('wrong_auth_token', '123456', 302),
498 ])
497 ])
499 def test_access_whitelisted_page_via_auth_token_bound_to_token(
498 def test_access_whitelisted_page_via_auth_token_bound_to_token(
500 self, test_name, auth_token, code, user_admin):
499 self, test_name, auth_token, code, user_admin):
501
500
502 expected_token = auth_token
501 expected_token = auth_token
503 if test_name == 'proper_auth_token':
502 if test_name == 'proper_auth_token':
504 auth_token = user_admin.api_key
503 auth_token = user_admin.api_key
505 expected_token = auth_token
504 expected_token = auth_token
506 assert auth_token
505 assert auth_token
507
506
508 whitelist = self._get_api_whitelist([
507 whitelist = self._get_api_whitelist([
509 'RepoCommitsView:repo_commit_raw@{}'.format(expected_token)])
508 'RepoCommitsView:repo_commit_raw@{}'.format(expected_token)])
510
509
511 with mock.patch.dict('rhodecode.CONFIG', whitelist):
510 with mock.patch.dict('rhodecode.CONFIG', whitelist):
512
511
513 with fixture.anon_access(False):
512 with fixture.anon_access(False):
514 self.app.get(
513 self.app.get(
515 route_path('repo_commit_raw',
514 route_path('repo_commit_raw',
516 repo_name=HG_REPO, commit_id='tip',
515 repo_name=HG_REPO, commit_id='tip',
517 params=dict(api_key=auth_token)),
516 params=dict(api_key=auth_token)),
518 status=code)
517 status=code)
519
518
520 def test_access_page_via_extra_auth_token(self):
519 def test_access_page_via_extra_auth_token(self):
521 whitelist = self._get_api_whitelist(whitelist_view)
520 whitelist = self._get_api_whitelist(whitelist_view)
522 with mock.patch.dict('rhodecode.CONFIG', whitelist):
521 with mock.patch.dict('rhodecode.CONFIG', whitelist):
523 assert whitelist_view == \
522 assert whitelist_view == \
524 whitelist['api_access_controllers_whitelist']
523 whitelist['api_access_controllers_whitelist']
525
524
526 new_auth_token = AuthTokenModel().create(
525 new_auth_token = AuthTokenModel().create(
527 TEST_USER_ADMIN_LOGIN, 'test')
526 TEST_USER_ADMIN_LOGIN, 'test')
528 Session().commit()
527 Session().commit()
529 with fixture.anon_access(False):
528 with fixture.anon_access(False):
530 self.app.get(
529 self.app.get(
531 route_path('repo_commit_raw',
530 route_path('repo_commit_raw',
532 repo_name=HG_REPO, commit_id='tip',
531 repo_name=HG_REPO, commit_id='tip',
533 params=dict(api_key=new_auth_token.api_key)),
532 params=dict(api_key=new_auth_token.api_key)),
534 status=200)
533 status=200)
535
534
536 def test_access_page_via_expired_auth_token(self):
535 def test_access_page_via_expired_auth_token(self):
537 whitelist = self._get_api_whitelist(whitelist_view)
536 whitelist = self._get_api_whitelist(whitelist_view)
538 with mock.patch.dict('rhodecode.CONFIG', whitelist):
537 with mock.patch.dict('rhodecode.CONFIG', whitelist):
539 assert whitelist_view == \
538 assert whitelist_view == \
540 whitelist['api_access_controllers_whitelist']
539 whitelist['api_access_controllers_whitelist']
541
540
542 new_auth_token = AuthTokenModel().create(
541 new_auth_token = AuthTokenModel().create(
543 TEST_USER_ADMIN_LOGIN, 'test')
542 TEST_USER_ADMIN_LOGIN, 'test')
544 Session().commit()
543 Session().commit()
545 # patch the api key and make it expired
544 # patch the api key and make it expired
546 new_auth_token.expires = 0
545 new_auth_token.expires = 0
547 Session().add(new_auth_token)
546 Session().add(new_auth_token)
548 Session().commit()
547 Session().commit()
549 with fixture.anon_access(False):
548 with fixture.anon_access(False):
550 self.app.get(
549 self.app.get(
551 route_path('repo_commit_raw',
550 route_path('repo_commit_raw',
552 repo_name=HG_REPO, commit_id='tip',
551 repo_name=HG_REPO, commit_id='tip',
553 params=dict(api_key=new_auth_token.api_key)),
552 params=dict(api_key=new_auth_token.api_key)),
554 status=302)
553 status=302)
@@ -1,427 +1,426 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import time
21 import time
22 import collections
22 import collections
23 import datetime
23 import datetime
24 import formencode
24 import formencode
25 import formencode.htmlfill
25 import formencode.htmlfill
26 import logging
26 import logging
27 import urlparse
27 import urlparse
28
28
29 from pyramid.httpexceptions import HTTPFound
29 from pyramid.httpexceptions import HTTPFound
30 from pyramid.view import view_config
30 from pyramid.view import view_config
31 from recaptcha.client.captcha import submit
31 from recaptcha.client.captcha import submit
32
32
33 from rhodecode.apps._base import BaseAppView
33 from rhodecode.apps._base import BaseAppView
34 from rhodecode.authentication.base import authenticate, HTTP_TYPE
34 from rhodecode.authentication.base import authenticate, HTTP_TYPE
35 from rhodecode.events import UserRegistered
35 from rhodecode.events import UserRegistered
36 from rhodecode.lib import helpers as h
36 from rhodecode.lib import helpers as h
37 from rhodecode.lib import audit_logger
37 from rhodecode.lib import audit_logger
38 from rhodecode.lib.auth import (
38 from rhodecode.lib.auth import (
39 AuthUser, HasPermissionAnyDecorator, CSRFRequired)
39 AuthUser, HasPermissionAnyDecorator, CSRFRequired)
40 from rhodecode.lib.base import get_ip_addr
40 from rhodecode.lib.base import get_ip_addr
41 from rhodecode.lib.exceptions import UserCreationError
41 from rhodecode.lib.exceptions import UserCreationError
42 from rhodecode.lib.utils2 import safe_str
42 from rhodecode.lib.utils2 import safe_str
43 from rhodecode.model.db import User, UserApiKeys
43 from rhodecode.model.db import User, UserApiKeys
44 from rhodecode.model.forms import LoginForm, RegisterForm, PasswordResetForm
44 from rhodecode.model.forms import LoginForm, RegisterForm, PasswordResetForm
45 from rhodecode.model.meta import Session
45 from rhodecode.model.meta import Session
46 from rhodecode.model.auth_token import AuthTokenModel
46 from rhodecode.model.auth_token import AuthTokenModel
47 from rhodecode.model.settings import SettingsModel
47 from rhodecode.model.settings import SettingsModel
48 from rhodecode.model.user import UserModel
48 from rhodecode.model.user import UserModel
49 from rhodecode.translation import _
49 from rhodecode.translation import _
50
50
51
51
52 log = logging.getLogger(__name__)
52 log = logging.getLogger(__name__)
53
53
54 CaptchaData = collections.namedtuple(
54 CaptchaData = collections.namedtuple(
55 'CaptchaData', 'active, private_key, public_key')
55 'CaptchaData', 'active, private_key, public_key')
56
56
57
57
58 def _store_user_in_session(session, username, remember=False):
58 def _store_user_in_session(session, username, remember=False):
59 user = User.get_by_username(username, case_insensitive=True)
59 user = User.get_by_username(username, case_insensitive=True)
60 auth_user = AuthUser(user.user_id)
60 auth_user = AuthUser(user.user_id)
61 auth_user.set_authenticated()
61 auth_user.set_authenticated()
62 cs = auth_user.get_cookie_store()
62 cs = auth_user.get_cookie_store()
63 session['rhodecode_user'] = cs
63 session['rhodecode_user'] = cs
64 user.update_lastlogin()
64 user.update_lastlogin()
65 Session().commit()
65 Session().commit()
66
66
67 # If they want to be remembered, update the cookie
67 # If they want to be remembered, update the cookie
68 if remember:
68 if remember:
69 _year = (datetime.datetime.now() +
69 _year = (datetime.datetime.now() +
70 datetime.timedelta(seconds=60 * 60 * 24 * 365))
70 datetime.timedelta(seconds=60 * 60 * 24 * 365))
71 session._set_cookie_expires(_year)
71 session._set_cookie_expires(_year)
72
72
73 session.save()
73 session.save()
74
74
75 safe_cs = cs.copy()
75 safe_cs = cs.copy()
76 safe_cs['password'] = '****'
76 safe_cs['password'] = '****'
77 log.info('user %s is now authenticated and stored in '
77 log.info('user %s is now authenticated and stored in '
78 'session, session attrs %s', username, safe_cs)
78 'session, session attrs %s', username, safe_cs)
79
79
80 # dumps session attrs back to cookie
80 # dumps session attrs back to cookie
81 session._update_cookie_out()
81 session._update_cookie_out()
82 # we set new cookie
82 # we set new cookie
83 headers = None
83 headers = None
84 if session.request['set_cookie']:
84 if session.request['set_cookie']:
85 # send set-cookie headers back to response to update cookie
85 # send set-cookie headers back to response to update cookie
86 headers = [('Set-Cookie', session.request['cookie_out'])]
86 headers = [('Set-Cookie', session.request['cookie_out'])]
87 return headers
87 return headers
88
88
89
89
90 def get_came_from(request):
90 def get_came_from(request):
91 came_from = safe_str(request.GET.get('came_from', ''))
91 came_from = safe_str(request.GET.get('came_from', ''))
92 parsed = urlparse.urlparse(came_from)
92 parsed = urlparse.urlparse(came_from)
93 allowed_schemes = ['http', 'https']
93 allowed_schemes = ['http', 'https']
94 default_came_from = h.route_path('home')
94 default_came_from = h.route_path('home')
95 if parsed.scheme and parsed.scheme not in allowed_schemes:
95 if parsed.scheme and parsed.scheme not in allowed_schemes:
96 log.error('Suspicious URL scheme detected %s for url %s' %
96 log.error('Suspicious URL scheme detected %s for url %s' %
97 (parsed.scheme, parsed))
97 (parsed.scheme, parsed))
98 came_from = default_came_from
98 came_from = default_came_from
99 elif parsed.netloc and request.host != parsed.netloc:
99 elif parsed.netloc and request.host != parsed.netloc:
100 log.error('Suspicious NETLOC detected %s for url %s server url '
100 log.error('Suspicious NETLOC detected %s for url %s server url '
101 'is: %s' % (parsed.netloc, parsed, request.host))
101 'is: %s' % (parsed.netloc, parsed, request.host))
102 came_from = default_came_from
102 came_from = default_came_from
103 elif any(bad_str in parsed.path for bad_str in ('\r', '\n')):
103 elif any(bad_str in parsed.path for bad_str in ('\r', '\n')):
104 log.error('Header injection detected `%s` for url %s server url ' %
104 log.error('Header injection detected `%s` for url %s server url ' %
105 (parsed.path, parsed))
105 (parsed.path, parsed))
106 came_from = default_came_from
106 came_from = default_came_from
107
107
108 return came_from or default_came_from
108 return came_from or default_came_from
109
109
110
110
111 class LoginView(BaseAppView):
111 class LoginView(BaseAppView):
112
112
113 def load_default_context(self):
113 def load_default_context(self):
114 c = self._get_local_tmpl_context()
114 c = self._get_local_tmpl_context()
115 c.came_from = get_came_from(self.request)
115 c.came_from = get_came_from(self.request)
116 self._register_global_c(c)
116
117 return c
117 return c
118
118
119 def _get_captcha_data(self):
119 def _get_captcha_data(self):
120 settings = SettingsModel().get_all_settings()
120 settings = SettingsModel().get_all_settings()
121 private_key = settings.get('rhodecode_captcha_private_key')
121 private_key = settings.get('rhodecode_captcha_private_key')
122 public_key = settings.get('rhodecode_captcha_public_key')
122 public_key = settings.get('rhodecode_captcha_public_key')
123 active = bool(private_key)
123 active = bool(private_key)
124 return CaptchaData(
124 return CaptchaData(
125 active=active, private_key=private_key, public_key=public_key)
125 active=active, private_key=private_key, public_key=public_key)
126
126
127 @view_config(
127 @view_config(
128 route_name='login', request_method='GET',
128 route_name='login', request_method='GET',
129 renderer='rhodecode:templates/login.mako')
129 renderer='rhodecode:templates/login.mako')
130 def login(self):
130 def login(self):
131 c = self.load_default_context()
131 c = self.load_default_context()
132 auth_user = self._rhodecode_user
132 auth_user = self._rhodecode_user
133
133
134 # redirect if already logged in
134 # redirect if already logged in
135 if (auth_user.is_authenticated and
135 if (auth_user.is_authenticated and
136 not auth_user.is_default and auth_user.ip_allowed):
136 not auth_user.is_default and auth_user.ip_allowed):
137 raise HTTPFound(c.came_from)
137 raise HTTPFound(c.came_from)
138
138
139 # check if we use headers plugin, and try to login using it.
139 # check if we use headers plugin, and try to login using it.
140 try:
140 try:
141 log.debug('Running PRE-AUTH for headers based authentication')
141 log.debug('Running PRE-AUTH for headers based authentication')
142 auth_info = authenticate(
142 auth_info = authenticate(
143 '', '', self.request.environ, HTTP_TYPE, skip_missing=True)
143 '', '', self.request.environ, HTTP_TYPE, skip_missing=True)
144 if auth_info:
144 if auth_info:
145 headers = _store_user_in_session(
145 headers = _store_user_in_session(
146 self.session, auth_info.get('username'))
146 self.session, auth_info.get('username'))
147 raise HTTPFound(c.came_from, headers=headers)
147 raise HTTPFound(c.came_from, headers=headers)
148 except UserCreationError as e:
148 except UserCreationError as e:
149 log.error(e)
149 log.error(e)
150 self.session.flash(e, queue='error')
150 self.session.flash(e, queue='error')
151
151
152 return self._get_template_context(c)
152 return self._get_template_context(c)
153
153
154 @view_config(
154 @view_config(
155 route_name='login', request_method='POST',
155 route_name='login', request_method='POST',
156 renderer='rhodecode:templates/login.mako')
156 renderer='rhodecode:templates/login.mako')
157 def login_post(self):
157 def login_post(self):
158 c = self.load_default_context()
158 c = self.load_default_context()
159
159
160 login_form = LoginForm()()
160 login_form = LoginForm(self.request.translate)()
161
161
162 try:
162 try:
163 self.session.invalidate()
163 self.session.invalidate()
164 form_result = login_form.to_python(self.request.POST)
164 form_result = login_form.to_python(self.request.POST)
165 # form checks for username/password, now we're authenticated
165 # form checks for username/password, now we're authenticated
166 headers = _store_user_in_session(
166 headers = _store_user_in_session(
167 self.session,
167 self.session,
168 username=form_result['username'],
168 username=form_result['username'],
169 remember=form_result['remember'])
169 remember=form_result['remember'])
170 log.debug('Redirecting to "%s" after login.', c.came_from)
170 log.debug('Redirecting to "%s" after login.', c.came_from)
171
171
172 audit_user = audit_logger.UserWrap(
172 audit_user = audit_logger.UserWrap(
173 username=self.request.POST.get('username'),
173 username=self.request.POST.get('username'),
174 ip_addr=self.request.remote_addr)
174 ip_addr=self.request.remote_addr)
175 action_data = {'user_agent': self.request.user_agent}
175 action_data = {'user_agent': self.request.user_agent}
176 audit_logger.store_web(
176 audit_logger.store_web(
177 'user.login.success', action_data=action_data,
177 'user.login.success', action_data=action_data,
178 user=audit_user, commit=True)
178 user=audit_user, commit=True)
179
179
180 raise HTTPFound(c.came_from, headers=headers)
180 raise HTTPFound(c.came_from, headers=headers)
181 except formencode.Invalid as errors:
181 except formencode.Invalid as errors:
182 defaults = errors.value
182 defaults = errors.value
183 # remove password from filling in form again
183 # remove password from filling in form again
184 defaults.pop('password', None)
184 defaults.pop('password', None)
185 render_ctx = self._get_template_context(c)
185 render_ctx = {
186 render_ctx.update({
187 'errors': errors.error_dict,
186 'errors': errors.error_dict,
188 'defaults': defaults,
187 'defaults': defaults,
189 })
188 }
190
189
191 audit_user = audit_logger.UserWrap(
190 audit_user = audit_logger.UserWrap(
192 username=self.request.POST.get('username'),
191 username=self.request.POST.get('username'),
193 ip_addr=self.request.remote_addr)
192 ip_addr=self.request.remote_addr)
194 action_data = {'user_agent': self.request.user_agent}
193 action_data = {'user_agent': self.request.user_agent}
195 audit_logger.store_web(
194 audit_logger.store_web(
196 'user.login.failure', action_data=action_data,
195 'user.login.failure', action_data=action_data,
197 user=audit_user, commit=True)
196 user=audit_user, commit=True)
198 return render_ctx
197 return self._get_template_context(c, **render_ctx)
199
198
200 except UserCreationError as e:
199 except UserCreationError as e:
201 # headers auth or other auth functions that create users on
200 # headers auth or other auth functions that create users on
202 # the fly can throw this exception signaling that there's issue
201 # the fly can throw this exception signaling that there's issue
203 # with user creation, explanation should be provided in
202 # with user creation, explanation should be provided in
204 # Exception itself
203 # Exception itself
205 self.session.flash(e, queue='error')
204 self.session.flash(e, queue='error')
206 return self._get_template_context(c)
205 return self._get_template_context(c)
207
206
208 @CSRFRequired()
207 @CSRFRequired()
209 @view_config(route_name='logout', request_method='POST')
208 @view_config(route_name='logout', request_method='POST')
210 def logout(self):
209 def logout(self):
211 auth_user = self._rhodecode_user
210 auth_user = self._rhodecode_user
212 log.info('Deleting session for user: `%s`', auth_user)
211 log.info('Deleting session for user: `%s`', auth_user)
213
212
214 action_data = {'user_agent': self.request.user_agent}
213 action_data = {'user_agent': self.request.user_agent}
215 audit_logger.store_web(
214 audit_logger.store_web(
216 'user.logout', action_data=action_data,
215 'user.logout', action_data=action_data,
217 user=auth_user, commit=True)
216 user=auth_user, commit=True)
218 self.session.delete()
217 self.session.delete()
219 return HTTPFound(h.route_path('home'))
218 return HTTPFound(h.route_path('home'))
220
219
221 @HasPermissionAnyDecorator(
220 @HasPermissionAnyDecorator(
222 'hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')
221 'hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')
223 @view_config(
222 @view_config(
224 route_name='register', request_method='GET',
223 route_name='register', request_method='GET',
225 renderer='rhodecode:templates/register.mako',)
224 renderer='rhodecode:templates/register.mako',)
226 def register(self, defaults=None, errors=None):
225 def register(self, defaults=None, errors=None):
227 c = self.load_default_context()
226 c = self.load_default_context()
228 defaults = defaults or {}
227 defaults = defaults or {}
229 errors = errors or {}
228 errors = errors or {}
230
229
231 settings = SettingsModel().get_all_settings()
230 settings = SettingsModel().get_all_settings()
232 register_message = settings.get('rhodecode_register_message') or ''
231 register_message = settings.get('rhodecode_register_message') or ''
233 captcha = self._get_captcha_data()
232 captcha = self._get_captcha_data()
234 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
233 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
235 .AuthUser().permissions['global']
234 .AuthUser().permissions['global']
236
235
237 render_ctx = self._get_template_context(c)
236 render_ctx = self._get_template_context(c)
238 render_ctx.update({
237 render_ctx.update({
239 'defaults': defaults,
238 'defaults': defaults,
240 'errors': errors,
239 'errors': errors,
241 'auto_active': auto_active,
240 'auto_active': auto_active,
242 'captcha_active': captcha.active,
241 'captcha_active': captcha.active,
243 'captcha_public_key': captcha.public_key,
242 'captcha_public_key': captcha.public_key,
244 'register_message': register_message,
243 'register_message': register_message,
245 })
244 })
246 return render_ctx
245 return render_ctx
247
246
248 @HasPermissionAnyDecorator(
247 @HasPermissionAnyDecorator(
249 'hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')
248 'hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')
250 @view_config(
249 @view_config(
251 route_name='register', request_method='POST',
250 route_name='register', request_method='POST',
252 renderer='rhodecode:templates/register.mako')
251 renderer='rhodecode:templates/register.mako')
253 def register_post(self):
252 def register_post(self):
254 captcha = self._get_captcha_data()
253 captcha = self._get_captcha_data()
255 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
254 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
256 .AuthUser().permissions['global']
255 .AuthUser().permissions['global']
257
256
258 register_form = RegisterForm()()
257 register_form = RegisterForm(self.request.translate)()
259 try:
258 try:
260
259
261 form_result = register_form.to_python(self.request.POST)
260 form_result = register_form.to_python(self.request.POST)
262 form_result['active'] = auto_active
261 form_result['active'] = auto_active
263
262
264 if captcha.active:
263 if captcha.active:
265 response = submit(
264 response = submit(
266 self.request.POST.get('recaptcha_challenge_field'),
265 self.request.POST.get('recaptcha_challenge_field'),
267 self.request.POST.get('recaptcha_response_field'),
266 self.request.POST.get('recaptcha_response_field'),
268 private_key=captcha.private_key,
267 private_key=captcha.private_key,
269 remoteip=get_ip_addr(self.request.environ))
268 remoteip=get_ip_addr(self.request.environ))
270 if not response.is_valid:
269 if not response.is_valid:
271 _value = form_result
270 _value = form_result
272 _msg = _('Bad captcha')
271 _msg = _('Bad captcha')
273 error_dict = {'recaptcha_field': _msg}
272 error_dict = {'recaptcha_field': _msg}
274 raise formencode.Invalid(_msg, _value, None,
273 raise formencode.Invalid(_msg, _value, None,
275 error_dict=error_dict)
274 error_dict=error_dict)
276
275
277 new_user = UserModel().create_registration(form_result)
276 new_user = UserModel().create_registration(form_result)
278 event = UserRegistered(user=new_user, session=self.session)
277 event = UserRegistered(user=new_user, session=self.session)
279 self.request.registry.notify(event)
278 self.request.registry.notify(event)
280 self.session.flash(
279 self.session.flash(
281 _('You have successfully registered with RhodeCode'),
280 _('You have successfully registered with RhodeCode'),
282 queue='success')
281 queue='success')
283 Session().commit()
282 Session().commit()
284
283
285 redirect_ro = self.request.route_path('login')
284 redirect_ro = self.request.route_path('login')
286 raise HTTPFound(redirect_ro)
285 raise HTTPFound(redirect_ro)
287
286
288 except formencode.Invalid as errors:
287 except formencode.Invalid as errors:
289 errors.value.pop('password', None)
288 errors.value.pop('password', None)
290 errors.value.pop('password_confirmation', None)
289 errors.value.pop('password_confirmation', None)
291 return self.register(
290 return self.register(
292 defaults=errors.value, errors=errors.error_dict)
291 defaults=errors.value, errors=errors.error_dict)
293
292
294 except UserCreationError as e:
293 except UserCreationError as e:
295 # container auth or other auth functions that create users on
294 # container auth or other auth functions that create users on
296 # the fly can throw this exception signaling that there's issue
295 # the fly can throw this exception signaling that there's issue
297 # with user creation, explanation should be provided in
296 # with user creation, explanation should be provided in
298 # Exception itself
297 # Exception itself
299 self.session.flash(e, queue='error')
298 self.session.flash(e, queue='error')
300 return self.register()
299 return self.register()
301
300
302 @view_config(
301 @view_config(
303 route_name='reset_password', request_method=('GET', 'POST'),
302 route_name='reset_password', request_method=('GET', 'POST'),
304 renderer='rhodecode:templates/password_reset.mako')
303 renderer='rhodecode:templates/password_reset.mako')
305 def password_reset(self):
304 def password_reset(self):
306 captcha = self._get_captcha_data()
305 captcha = self._get_captcha_data()
307
306
308 render_ctx = {
307 render_ctx = {
309 'captcha_active': captcha.active,
308 'captcha_active': captcha.active,
310 'captcha_public_key': captcha.public_key,
309 'captcha_public_key': captcha.public_key,
311 'defaults': {},
310 'defaults': {},
312 'errors': {},
311 'errors': {},
313 }
312 }
314
313
315 # always send implicit message to prevent from discovery of
314 # always send implicit message to prevent from discovery of
316 # matching emails
315 # matching emails
317 msg = _('If such email exists, a password reset link was sent to it.')
316 msg = _('If such email exists, a password reset link was sent to it.')
318
317
319 if self.request.POST:
318 if self.request.POST:
320 if h.HasPermissionAny('hg.password_reset.disabled')():
319 if h.HasPermissionAny('hg.password_reset.disabled')():
321 _email = self.request.POST.get('email', '')
320 _email = self.request.POST.get('email', '')
322 log.error('Failed attempt to reset password for `%s`.', _email)
321 log.error('Failed attempt to reset password for `%s`.', _email)
323 self.session.flash(_('Password reset has been disabled.'),
322 self.session.flash(_('Password reset has been disabled.'),
324 queue='error')
323 queue='error')
325 return HTTPFound(self.request.route_path('reset_password'))
324 return HTTPFound(self.request.route_path('reset_password'))
326
325
327 password_reset_form = PasswordResetForm()()
326 password_reset_form = PasswordResetForm(self.request.translate)()
328 try:
327 try:
329 form_result = password_reset_form.to_python(
328 form_result = password_reset_form.to_python(
330 self.request.POST)
329 self.request.POST)
331 user_email = form_result['email']
330 user_email = form_result['email']
332
331
333 if captcha.active:
332 if captcha.active:
334 response = submit(
333 response = submit(
335 self.request.POST.get('recaptcha_challenge_field'),
334 self.request.POST.get('recaptcha_challenge_field'),
336 self.request.POST.get('recaptcha_response_field'),
335 self.request.POST.get('recaptcha_response_field'),
337 private_key=captcha.private_key,
336 private_key=captcha.private_key,
338 remoteip=get_ip_addr(self.request.environ))
337 remoteip=get_ip_addr(self.request.environ))
339 if not response.is_valid:
338 if not response.is_valid:
340 _value = form_result
339 _value = form_result
341 _msg = _('Bad captcha')
340 _msg = _('Bad captcha')
342 error_dict = {'recaptcha_field': _msg}
341 error_dict = {'recaptcha_field': _msg}
343 raise formencode.Invalid(
342 raise formencode.Invalid(
344 _msg, _value, None, error_dict=error_dict)
343 _msg, _value, None, error_dict=error_dict)
345
344
346 # Generate reset URL and send mail.
345 # Generate reset URL and send mail.
347 user = User.get_by_email(user_email)
346 user = User.get_by_email(user_email)
348
347
349 # generate password reset token that expires in 10minutes
348 # generate password reset token that expires in 10minutes
350 desc = 'Generated token for password reset from {}'.format(
349 desc = 'Generated token for password reset from {}'.format(
351 datetime.datetime.now().isoformat())
350 datetime.datetime.now().isoformat())
352 reset_token = AuthTokenModel().create(
351 reset_token = AuthTokenModel().create(
353 user, lifetime=10,
352 user, lifetime=10,
354 description=desc,
353 description=desc,
355 role=UserApiKeys.ROLE_PASSWORD_RESET)
354 role=UserApiKeys.ROLE_PASSWORD_RESET)
356 Session().commit()
355 Session().commit()
357
356
358 log.debug('Successfully created password recovery token')
357 log.debug('Successfully created password recovery token')
359 password_reset_url = self.request.route_url(
358 password_reset_url = self.request.route_url(
360 'reset_password_confirmation',
359 'reset_password_confirmation',
361 _query={'key': reset_token.api_key})
360 _query={'key': reset_token.api_key})
362 UserModel().reset_password_link(
361 UserModel().reset_password_link(
363 form_result, password_reset_url)
362 form_result, password_reset_url)
364 # Display success message and redirect.
363 # Display success message and redirect.
365 self.session.flash(msg, queue='success')
364 self.session.flash(msg, queue='success')
366
365
367 action_data = {'email': user_email,
366 action_data = {'email': user_email,
368 'user_agent': self.request.user_agent}
367 'user_agent': self.request.user_agent}
369 audit_logger.store_web(
368 audit_logger.store_web(
370 'user.password.reset_request', action_data=action_data,
369 'user.password.reset_request', action_data=action_data,
371 user=self._rhodecode_user, commit=True)
370 user=self._rhodecode_user, commit=True)
372 return HTTPFound(self.request.route_path('reset_password'))
371 return HTTPFound(self.request.route_path('reset_password'))
373
372
374 except formencode.Invalid as errors:
373 except formencode.Invalid as errors:
375 render_ctx.update({
374 render_ctx.update({
376 'defaults': errors.value,
375 'defaults': errors.value,
377 'errors': errors.error_dict,
376 'errors': errors.error_dict,
378 })
377 })
379 if not self.request.POST.get('email'):
378 if not self.request.POST.get('email'):
380 # case of empty email, we want to report that
379 # case of empty email, we want to report that
381 return render_ctx
380 return render_ctx
382
381
383 if 'recaptcha_field' in errors.error_dict:
382 if 'recaptcha_field' in errors.error_dict:
384 # case of failed captcha
383 # case of failed captcha
385 return render_ctx
384 return render_ctx
386
385
387 log.debug('faking response on invalid password reset')
386 log.debug('faking response on invalid password reset')
388 # make this take 2s, to prevent brute forcing.
387 # make this take 2s, to prevent brute forcing.
389 time.sleep(2)
388 time.sleep(2)
390 self.session.flash(msg, queue='success')
389 self.session.flash(msg, queue='success')
391 return HTTPFound(self.request.route_path('reset_password'))
390 return HTTPFound(self.request.route_path('reset_password'))
392
391
393 return render_ctx
392 return render_ctx
394
393
395 @view_config(route_name='reset_password_confirmation',
394 @view_config(route_name='reset_password_confirmation',
396 request_method='GET')
395 request_method='GET')
397 def password_reset_confirmation(self):
396 def password_reset_confirmation(self):
398
397
399 if self.request.GET and self.request.GET.get('key'):
398 if self.request.GET and self.request.GET.get('key'):
400 # make this take 2s, to prevent brute forcing.
399 # make this take 2s, to prevent brute forcing.
401 time.sleep(2)
400 time.sleep(2)
402
401
403 token = AuthTokenModel().get_auth_token(
402 token = AuthTokenModel().get_auth_token(
404 self.request.GET.get('key'))
403 self.request.GET.get('key'))
405
404
406 # verify token is the correct role
405 # verify token is the correct role
407 if token is None or token.role != UserApiKeys.ROLE_PASSWORD_RESET:
406 if token is None or token.role != UserApiKeys.ROLE_PASSWORD_RESET:
408 log.debug('Got token with role:%s expected is %s',
407 log.debug('Got token with role:%s expected is %s',
409 getattr(token, 'role', 'EMPTY_TOKEN'),
408 getattr(token, 'role', 'EMPTY_TOKEN'),
410 UserApiKeys.ROLE_PASSWORD_RESET)
409 UserApiKeys.ROLE_PASSWORD_RESET)
411 self.session.flash(
410 self.session.flash(
412 _('Given reset token is invalid'), queue='error')
411 _('Given reset token is invalid'), queue='error')
413 return HTTPFound(self.request.route_path('reset_password'))
412 return HTTPFound(self.request.route_path('reset_password'))
414
413
415 try:
414 try:
416 owner = token.user
415 owner = token.user
417 data = {'email': owner.email, 'token': token.api_key}
416 data = {'email': owner.email, 'token': token.api_key}
418 UserModel().reset_password(data)
417 UserModel().reset_password(data)
419 self.session.flash(
418 self.session.flash(
420 _('Your password reset was successful, '
419 _('Your password reset was successful, '
421 'a new password has been sent to your email'),
420 'a new password has been sent to your email'),
422 queue='success')
421 queue='success')
423 except Exception as e:
422 except Exception as e:
424 log.error(e)
423 log.error(e)
425 return HTTPFound(self.request.route_path('reset_password'))
424 return HTTPFound(self.request.route_path('reset_password'))
426
425
427 return HTTPFound(self.request.route_path('login'))
426 return HTTPFound(self.request.route_path('login'))
@@ -1,205 +1,203 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 # -*- coding: utf-8 -*-
20 # -*- coding: utf-8 -*-
21
21
22 # Copyright (C) 2016-2017 RhodeCode GmbH
22 # Copyright (C) 2016-2017 RhodeCode GmbH
23 #
23 #
24 # This program is free software: you can redistribute it and/or modify
24 # This program is free software: you can redistribute it and/or modify
25 # it under the terms of the GNU Affero General Public License, version 3
25 # it under the terms of the GNU Affero General Public License, version 3
26 # (only), as published by the Free Software Foundation.
26 # (only), as published by the Free Software Foundation.
27 #
27 #
28 # This program is distributed in the hope that it will be useful,
28 # This program is distributed in the hope that it will be useful,
29 # but WITHOUT ANY WARRANTY; without even the implied warranty of
29 # but WITHOUT ANY WARRANTY; without even the implied warranty of
30 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
30 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
31 # GNU General Public License for more details.
31 # GNU General Public License for more details.
32 #
32 #
33 # You should have received a copy of the GNU Affero General Public License
33 # You should have received a copy of the GNU Affero General Public License
34 # along with this program. If not, see <http://www.gnu.org/licenses/>.
34 # along with this program. If not, see <http://www.gnu.org/licenses/>.
35 #
35 #
36 # This program is dual-licensed. If you wish to learn more about the
36 # This program is dual-licensed. If you wish to learn more about the
37 # RhodeCode Enterprise Edition, including its added features, Support services,
37 # RhodeCode Enterprise Edition, including its added features, Support services,
38 # and proprietary license terms, please see https://rhodecode.com/licenses/
38 # and proprietary license terms, please see https://rhodecode.com/licenses/
39
39
40 import pytest
40 import pytest
41
41
42 from rhodecode.model.db import User
42 from rhodecode.model.db import User
43 from rhodecode.tests import TestController, assert_session_flash
43 from rhodecode.tests import TestController, assert_session_flash
44 from rhodecode.lib import helpers as h
44 from rhodecode.lib import helpers as h
45
45
46
46
47 def route_path(name, params=None, **kwargs):
47 def route_path(name, params=None, **kwargs):
48 import urllib
48 import urllib
49 from rhodecode.apps._base import ADMIN_PREFIX
49 from rhodecode.apps._base import ADMIN_PREFIX
50
50
51 base_url = {
51 base_url = {
52 'my_account_edit': ADMIN_PREFIX + '/my_account/edit',
52 'my_account_edit': ADMIN_PREFIX + '/my_account/edit',
53 'my_account_update': ADMIN_PREFIX + '/my_account/update',
53 'my_account_update': ADMIN_PREFIX + '/my_account/update',
54 'my_account_pullrequests': ADMIN_PREFIX + '/my_account/pull_requests',
54 'my_account_pullrequests': ADMIN_PREFIX + '/my_account/pull_requests',
55 'my_account_pullrequests_data': ADMIN_PREFIX + '/my_account/pull_requests/data',
55 'my_account_pullrequests_data': ADMIN_PREFIX + '/my_account/pull_requests/data',
56 }[name].format(**kwargs)
56 }[name].format(**kwargs)
57
57
58 if params:
58 if params:
59 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
59 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
60 return base_url
60 return base_url
61
61
62
62
63 class TestMyAccountEdit(TestController):
63 class TestMyAccountEdit(TestController):
64
64
65 def test_my_account_edit(self):
65 def test_my_account_edit(self):
66 self.log_user()
66 self.log_user()
67 response = self.app.get(route_path('my_account_edit'))
67 response = self.app.get(route_path('my_account_edit'))
68
68
69 response.mustcontain('value="test_admin')
69 response.mustcontain('value="test_admin')
70
70
71 @pytest.mark.backends("git", "hg")
71 @pytest.mark.backends("git", "hg")
72 def test_my_account_my_pullrequests(self, pr_util):
72 def test_my_account_my_pullrequests(self, pr_util):
73 self.log_user()
73 self.log_user()
74 response = self.app.get(route_path('my_account_pullrequests'))
74 response = self.app.get(route_path('my_account_pullrequests'))
75 response.mustcontain('There are currently no open pull '
75 response.mustcontain('There are currently no open pull '
76 'requests requiring your participation.')
76 'requests requiring your participation.')
77
77
78 @pytest.mark.backends("git", "hg")
78 @pytest.mark.backends("git", "hg")
79 def test_my_account_my_pullrequests_data(self, pr_util, xhr_header):
79 def test_my_account_my_pullrequests_data(self, pr_util, xhr_header):
80 self.log_user()
80 self.log_user()
81 response = self.app.get(route_path('my_account_pullrequests_data'),
81 response = self.app.get(route_path('my_account_pullrequests_data'),
82 extra_environ=xhr_header)
82 extra_environ=xhr_header)
83 assert response.json == {
83 assert response.json == {
84 u'data': [], u'draw': None,
84 u'data': [], u'draw': None,
85 u'recordsFiltered': 0, u'recordsTotal': 0}
85 u'recordsFiltered': 0, u'recordsTotal': 0}
86
86
87 pr = pr_util.create_pull_request(title='TestMyAccountPR')
87 pr = pr_util.create_pull_request(title='TestMyAccountPR')
88 expected = {
88 expected = {
89 'author_raw': 'RhodeCode Admin',
89 'author_raw': 'RhodeCode Admin',
90 'name_raw': pr.pull_request_id
90 'name_raw': pr.pull_request_id
91 }
91 }
92 response = self.app.get(route_path('my_account_pullrequests_data'),
92 response = self.app.get(route_path('my_account_pullrequests_data'),
93 extra_environ=xhr_header)
93 extra_environ=xhr_header)
94 assert response.json['recordsTotal'] == 1
94 assert response.json['recordsTotal'] == 1
95 assert response.json['data'][0]['author_raw'] == expected['author_raw']
95 assert response.json['data'][0]['author_raw'] == expected['author_raw']
96
96
97 assert response.json['data'][0]['author_raw'] == expected['author_raw']
97 assert response.json['data'][0]['author_raw'] == expected['author_raw']
98 assert response.json['data'][0]['name_raw'] == expected['name_raw']
98 assert response.json['data'][0]['name_raw'] == expected['name_raw']
99
99
100 @pytest.mark.parametrize(
100 @pytest.mark.parametrize(
101 "name, attrs", [
101 "name, attrs", [
102 ('firstname', {'firstname': 'new_username'}),
102 ('firstname', {'firstname': 'new_username'}),
103 ('lastname', {'lastname': 'new_username'}),
103 ('lastname', {'lastname': 'new_username'}),
104 ('admin', {'admin': True}),
104 ('admin', {'admin': True}),
105 ('admin', {'admin': False}),
105 ('admin', {'admin': False}),
106 ('extern_type', {'extern_type': 'ldap'}),
106 ('extern_type', {'extern_type': 'ldap'}),
107 ('extern_type', {'extern_type': None}),
107 ('extern_type', {'extern_type': None}),
108 # ('extern_name', {'extern_name': 'test'}),
108 # ('extern_name', {'extern_name': 'test'}),
109 # ('extern_name', {'extern_name': None}),
109 # ('extern_name', {'extern_name': None}),
110 ('active', {'active': False}),
110 ('active', {'active': False}),
111 ('active', {'active': True}),
111 ('active', {'active': True}),
112 ('email', {'email': 'some@email.com'}),
112 ('email', {'email': 'some@email.com'}),
113 ])
113 ])
114 def test_my_account_update(self, name, attrs, user_util):
114 def test_my_account_update(self, name, attrs, user_util):
115 usr = user_util.create_user(password='qweqwe')
115 usr = user_util.create_user(password='qweqwe')
116 params = usr.get_api_data() # current user data
116 params = usr.get_api_data() # current user data
117 user_id = usr.user_id
117 user_id = usr.user_id
118 self.log_user(
118 self.log_user(
119 username=usr.username, password='qweqwe')
119 username=usr.username, password='qweqwe')
120
120
121 params.update({'password_confirmation': ''})
121 params.update({'password_confirmation': ''})
122 params.update({'new_password': ''})
122 params.update({'new_password': ''})
123 params.update({'extern_type': 'rhodecode'})
123 params.update({'extern_type': 'rhodecode'})
124 params.update({'extern_name': 'rhodecode'})
124 params.update({'extern_name': 'rhodecode'})
125 params.update({'csrf_token': self.csrf_token})
125 params.update({'csrf_token': self.csrf_token})
126
126
127 params.update(attrs)
127 params.update(attrs)
128 # my account page cannot set language param yet, only for admins
128 # my account page cannot set language param yet, only for admins
129 del params['language']
129 del params['language']
130 response = self.app.post(route_path('my_account_update'), params)
130 response = self.app.post(route_path('my_account_update'), params)
131
131
132 assert_session_flash(
132 assert_session_flash(
133 response, 'Your account was updated successfully')
133 response, 'Your account was updated successfully')
134
134
135 del params['csrf_token']
135 del params['csrf_token']
136
136
137 updated_user = User.get(user_id)
137 updated_user = User.get(user_id)
138 updated_params = updated_user.get_api_data()
138 updated_params = updated_user.get_api_data()
139 updated_params.update({'password_confirmation': ''})
139 updated_params.update({'password_confirmation': ''})
140 updated_params.update({'new_password': ''})
140 updated_params.update({'new_password': ''})
141
141
142 params['last_login'] = updated_params['last_login']
142 params['last_login'] = updated_params['last_login']
143 params['last_activity'] = updated_params['last_activity']
143 params['last_activity'] = updated_params['last_activity']
144 # my account page cannot set language param yet, only for admins
144 # my account page cannot set language param yet, only for admins
145 # but we get this info from API anyway
145 # but we get this info from API anyway
146 params['language'] = updated_params['language']
146 params['language'] = updated_params['language']
147
147
148 if name == 'email':
148 if name == 'email':
149 params['emails'] = [attrs['email']]
149 params['emails'] = [attrs['email']]
150 if name == 'extern_type':
150 if name == 'extern_type':
151 # cannot update this via form, expected value is original one
151 # cannot update this via form, expected value is original one
152 params['extern_type'] = "rhodecode"
152 params['extern_type'] = "rhodecode"
153 if name == 'extern_name':
153 if name == 'extern_name':
154 # cannot update this via form, expected value is original one
154 # cannot update this via form, expected value is original one
155 params['extern_name'] = str(user_id)
155 params['extern_name'] = str(user_id)
156 if name == 'active':
156 if name == 'active':
157 # my account cannot deactivate account
157 # my account cannot deactivate account
158 params['active'] = True
158 params['active'] = True
159 if name == 'admin':
159 if name == 'admin':
160 # my account cannot make you an admin !
160 # my account cannot make you an admin !
161 params['admin'] = False
161 params['admin'] = False
162
162
163 assert params == updated_params
163 assert params == updated_params
164
164
165 def test_my_account_update_err_email_exists(self):
165 def test_my_account_update_err_email_exists(self):
166 self.log_user()
166 self.log_user()
167
167
168 new_email = 'test_regular@mail.com' # already existing email
168 new_email = 'test_regular@mail.com' # already existing email
169 params = {
169 params = {
170 'username': 'test_admin',
170 'username': 'test_admin',
171 'new_password': 'test12',
171 'new_password': 'test12',
172 'password_confirmation': 'test122',
172 'password_confirmation': 'test122',
173 'firstname': 'NewName',
173 'firstname': 'NewName',
174 'lastname': 'NewLastname',
174 'lastname': 'NewLastname',
175 'email': new_email,
175 'email': new_email,
176 'csrf_token': self.csrf_token,
176 'csrf_token': self.csrf_token,
177 }
177 }
178
178
179 response = self.app.post(route_path('my_account_update'),
179 response = self.app.post(route_path('my_account_update'),
180 params=params)
180 params=params)
181
181
182 response.mustcontain('This e-mail address is already taken')
182 response.mustcontain('This e-mail address is already taken')
183
183
184 def test_my_account_update_bad_email_address(self):
184 def test_my_account_update_bad_email_address(self):
185 self.log_user('test_regular2', 'test12')
185 self.log_user('test_regular2', 'test12')
186
186
187 new_email = 'newmail.pl'
187 new_email = 'newmail.pl'
188 params = {
188 params = {
189 'username': 'test_admin',
189 'username': 'test_admin',
190 'new_password': 'test12',
190 'new_password': 'test12',
191 'password_confirmation': 'test122',
191 'password_confirmation': 'test122',
192 'firstname': 'NewName',
192 'firstname': 'NewName',
193 'lastname': 'NewLastname',
193 'lastname': 'NewLastname',
194 'email': new_email,
194 'email': new_email,
195 'csrf_token': self.csrf_token,
195 'csrf_token': self.csrf_token,
196 }
196 }
197 response = self.app.post(route_path('my_account_update'),
197 response = self.app.post(route_path('my_account_update'),
198 params=params)
198 params=params)
199
199
200 response.mustcontain('An email address must contain a single @')
200 response.mustcontain('An email address must contain a single @')
201 from rhodecode.model import validators
201 msg = '???'
202 msg = validators.ValidUsername(
203 edit=False, old_data={})._messages['username_exists']
204 msg = h.html_escape(msg % {'username': 'test_admin'})
202 msg = h.html_escape(msg % {'username': 'test_admin'})
205 response.mustcontain(u"%s" % msg)
203 response.mustcontain(u"%s" % msg)
@@ -1,580 +1,584 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 import datetime
22 import datetime
23
23
24 import formencode
24 import formencode
25 import formencode.htmlfill
25 import formencode.htmlfill
26 from pyramid.httpexceptions import HTTPFound
26 from pyramid.httpexceptions import HTTPFound
27 from pyramid.view import view_config
27 from pyramid.view import view_config
28 from pyramid.renderers import render
28 from pyramid.renderers import render
29 from pyramid.response import Response
29 from pyramid.response import Response
30
30
31 from rhodecode.apps._base import BaseAppView, DataGridAppView
31 from rhodecode.apps._base import BaseAppView, DataGridAppView
32 from rhodecode import forms
32 from rhodecode import forms
33 from rhodecode.lib import helpers as h
33 from rhodecode.lib import helpers as h
34 from rhodecode.lib import audit_logger
34 from rhodecode.lib import audit_logger
35 from rhodecode.lib.ext_json import json
35 from rhodecode.lib.ext_json import json
36 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
36 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
37 from rhodecode.lib.channelstream import (
37 from rhodecode.lib.channelstream import (
38 channelstream_request, ChannelstreamException)
38 channelstream_request, ChannelstreamException)
39 from rhodecode.lib.utils2 import safe_int, md5, str2bool
39 from rhodecode.lib.utils2 import safe_int, md5, str2bool
40 from rhodecode.model.auth_token import AuthTokenModel
40 from rhodecode.model.auth_token import AuthTokenModel
41 from rhodecode.model.comment import CommentsModel
41 from rhodecode.model.comment import CommentsModel
42 from rhodecode.model.db import (
42 from rhodecode.model.db import (
43 Repository, UserEmailMap, UserApiKeys, UserFollowing, joinedload,
43 Repository, UserEmailMap, UserApiKeys, UserFollowing, joinedload,
44 PullRequest)
44 PullRequest)
45 from rhodecode.model.forms import UserForm
45 from rhodecode.model.forms import UserForm, UserExtraEmailForm
46 from rhodecode.model.meta import Session
46 from rhodecode.model.meta import Session
47 from rhodecode.model.pull_request import PullRequestModel
47 from rhodecode.model.pull_request import PullRequestModel
48 from rhodecode.model.scm import RepoList
48 from rhodecode.model.scm import RepoList
49 from rhodecode.model.user import UserModel
49 from rhodecode.model.user import UserModel
50 from rhodecode.model.repo import RepoModel
50 from rhodecode.model.repo import RepoModel
51 from rhodecode.model.validation_schema.schemas import user_schema
51 from rhodecode.model.validation_schema.schemas import user_schema
52
52
53 log = logging.getLogger(__name__)
53 log = logging.getLogger(__name__)
54
54
55
55
56 class MyAccountView(BaseAppView, DataGridAppView):
56 class MyAccountView(BaseAppView, DataGridAppView):
57 ALLOW_SCOPED_TOKENS = False
57 ALLOW_SCOPED_TOKENS = False
58 """
58 """
59 This view has alternative version inside EE, if modified please take a look
59 This view has alternative version inside EE, if modified please take a look
60 in there as well.
60 in there as well.
61 """
61 """
62
62
63 def load_default_context(self):
63 def load_default_context(self):
64 c = self._get_local_tmpl_context()
64 c = self._get_local_tmpl_context()
65 c.user = c.auth_user.get_instance()
65 c.user = c.auth_user.get_instance()
66 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
66 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
67 self._register_global_c(c)
67
68 return c
68 return c
69
69
70 @LoginRequired()
70 @LoginRequired()
71 @NotAnonymous()
71 @NotAnonymous()
72 @view_config(
72 @view_config(
73 route_name='my_account_profile', request_method='GET',
73 route_name='my_account_profile', request_method='GET',
74 renderer='rhodecode:templates/admin/my_account/my_account.mako')
74 renderer='rhodecode:templates/admin/my_account/my_account.mako')
75 def my_account_profile(self):
75 def my_account_profile(self):
76 c = self.load_default_context()
76 c = self.load_default_context()
77 c.active = 'profile'
77 c.active = 'profile'
78 return self._get_template_context(c)
78 return self._get_template_context(c)
79
79
80 @LoginRequired()
80 @LoginRequired()
81 @NotAnonymous()
81 @NotAnonymous()
82 @view_config(
82 @view_config(
83 route_name='my_account_password', request_method='GET',
83 route_name='my_account_password', request_method='GET',
84 renderer='rhodecode:templates/admin/my_account/my_account.mako')
84 renderer='rhodecode:templates/admin/my_account/my_account.mako')
85 def my_account_password(self):
85 def my_account_password(self):
86 c = self.load_default_context()
86 c = self.load_default_context()
87 c.active = 'password'
87 c.active = 'password'
88 c.extern_type = c.user.extern_type
88 c.extern_type = c.user.extern_type
89
89
90 schema = user_schema.ChangePasswordSchema().bind(
90 schema = user_schema.ChangePasswordSchema().bind(
91 username=c.user.username)
91 username=c.user.username)
92
92
93 form = forms.Form(
93 form = forms.Form(
94 schema,
94 schema,
95 action=h.route_path('my_account_password_update'),
95 action=h.route_path('my_account_password_update'),
96 buttons=(forms.buttons.save, forms.buttons.reset))
96 buttons=(forms.buttons.save, forms.buttons.reset))
97
97
98 c.form = form
98 c.form = form
99 return self._get_template_context(c)
99 return self._get_template_context(c)
100
100
101 @LoginRequired()
101 @LoginRequired()
102 @NotAnonymous()
102 @NotAnonymous()
103 @CSRFRequired()
103 @CSRFRequired()
104 @view_config(
104 @view_config(
105 route_name='my_account_password_update', request_method='POST',
105 route_name='my_account_password_update', request_method='POST',
106 renderer='rhodecode:templates/admin/my_account/my_account.mako')
106 renderer='rhodecode:templates/admin/my_account/my_account.mako')
107 def my_account_password_update(self):
107 def my_account_password_update(self):
108 _ = self.request.translate
108 _ = self.request.translate
109 c = self.load_default_context()
109 c = self.load_default_context()
110 c.active = 'password'
110 c.active = 'password'
111 c.extern_type = c.user.extern_type
111 c.extern_type = c.user.extern_type
112
112
113 schema = user_schema.ChangePasswordSchema().bind(
113 schema = user_schema.ChangePasswordSchema().bind(
114 username=c.user.username)
114 username=c.user.username)
115
115
116 form = forms.Form(
116 form = forms.Form(
117 schema, buttons=(forms.buttons.save, forms.buttons.reset))
117 schema, buttons=(forms.buttons.save, forms.buttons.reset))
118
118
119 if c.extern_type != 'rhodecode':
119 if c.extern_type != 'rhodecode':
120 raise HTTPFound(self.request.route_path('my_account_password'))
120 raise HTTPFound(self.request.route_path('my_account_password'))
121
121
122 controls = self.request.POST.items()
122 controls = self.request.POST.items()
123 try:
123 try:
124 valid_data = form.validate(controls)
124 valid_data = form.validate(controls)
125 UserModel().update_user(c.user.user_id, **valid_data)
125 UserModel().update_user(c.user.user_id, **valid_data)
126 c.user.update_userdata(force_password_change=False)
126 c.user.update_userdata(force_password_change=False)
127 Session().commit()
127 Session().commit()
128 except forms.ValidationFailure as e:
128 except forms.ValidationFailure as e:
129 c.form = e
129 c.form = e
130 return self._get_template_context(c)
130 return self._get_template_context(c)
131
131
132 except Exception:
132 except Exception:
133 log.exception("Exception updating password")
133 log.exception("Exception updating password")
134 h.flash(_('Error occurred during update of user password'),
134 h.flash(_('Error occurred during update of user password'),
135 category='error')
135 category='error')
136 else:
136 else:
137 instance = c.auth_user.get_instance()
137 instance = c.auth_user.get_instance()
138 self.session.setdefault('rhodecode_user', {}).update(
138 self.session.setdefault('rhodecode_user', {}).update(
139 {'password': md5(instance.password)})
139 {'password': md5(instance.password)})
140 self.session.save()
140 self.session.save()
141 h.flash(_("Successfully updated password"), category='success')
141 h.flash(_("Successfully updated password"), category='success')
142
142
143 raise HTTPFound(self.request.route_path('my_account_password'))
143 raise HTTPFound(self.request.route_path('my_account_password'))
144
144
145 @LoginRequired()
145 @LoginRequired()
146 @NotAnonymous()
146 @NotAnonymous()
147 @view_config(
147 @view_config(
148 route_name='my_account_auth_tokens', request_method='GET',
148 route_name='my_account_auth_tokens', request_method='GET',
149 renderer='rhodecode:templates/admin/my_account/my_account.mako')
149 renderer='rhodecode:templates/admin/my_account/my_account.mako')
150 def my_account_auth_tokens(self):
150 def my_account_auth_tokens(self):
151 _ = self.request.translate
151 _ = self.request.translate
152
152
153 c = self.load_default_context()
153 c = self.load_default_context()
154 c.active = 'auth_tokens'
154 c.active = 'auth_tokens'
155 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
155 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
156 c.role_values = [
156 c.role_values = [
157 (x, AuthTokenModel.cls._get_role_name(x))
157 (x, AuthTokenModel.cls._get_role_name(x))
158 for x in AuthTokenModel.cls.ROLES]
158 for x in AuthTokenModel.cls.ROLES]
159 c.role_options = [(c.role_values, _("Role"))]
159 c.role_options = [(c.role_values, _("Role"))]
160 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
160 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
161 c.user.user_id, show_expired=True)
161 c.user.user_id, show_expired=True)
162 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
162 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
163 return self._get_template_context(c)
163 return self._get_template_context(c)
164
164
165 def maybe_attach_token_scope(self, token):
165 def maybe_attach_token_scope(self, token):
166 # implemented in EE edition
166 # implemented in EE edition
167 pass
167 pass
168
168
169 @LoginRequired()
169 @LoginRequired()
170 @NotAnonymous()
170 @NotAnonymous()
171 @CSRFRequired()
171 @CSRFRequired()
172 @view_config(
172 @view_config(
173 route_name='my_account_auth_tokens_add', request_method='POST',)
173 route_name='my_account_auth_tokens_add', request_method='POST',)
174 def my_account_auth_tokens_add(self):
174 def my_account_auth_tokens_add(self):
175 _ = self.request.translate
175 _ = self.request.translate
176 c = self.load_default_context()
176 c = self.load_default_context()
177
177
178 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
178 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
179 description = self.request.POST.get('description')
179 description = self.request.POST.get('description')
180 role = self.request.POST.get('role')
180 role = self.request.POST.get('role')
181
181
182 token = AuthTokenModel().create(
182 token = AuthTokenModel().create(
183 c.user.user_id, description, lifetime, role)
183 c.user.user_id, description, lifetime, role)
184 token_data = token.get_api_data()
184 token_data = token.get_api_data()
185
185
186 self.maybe_attach_token_scope(token)
186 self.maybe_attach_token_scope(token)
187 audit_logger.store_web(
187 audit_logger.store_web(
188 'user.edit.token.add', action_data={
188 'user.edit.token.add', action_data={
189 'data': {'token': token_data, 'user': 'self'}},
189 'data': {'token': token_data, 'user': 'self'}},
190 user=self._rhodecode_user, )
190 user=self._rhodecode_user, )
191 Session().commit()
191 Session().commit()
192
192
193 h.flash(_("Auth token successfully created"), category='success')
193 h.flash(_("Auth token successfully created"), category='success')
194 return HTTPFound(h.route_path('my_account_auth_tokens'))
194 return HTTPFound(h.route_path('my_account_auth_tokens'))
195
195
196 @LoginRequired()
196 @LoginRequired()
197 @NotAnonymous()
197 @NotAnonymous()
198 @CSRFRequired()
198 @CSRFRequired()
199 @view_config(
199 @view_config(
200 route_name='my_account_auth_tokens_delete', request_method='POST')
200 route_name='my_account_auth_tokens_delete', request_method='POST')
201 def my_account_auth_tokens_delete(self):
201 def my_account_auth_tokens_delete(self):
202 _ = self.request.translate
202 _ = self.request.translate
203 c = self.load_default_context()
203 c = self.load_default_context()
204
204
205 del_auth_token = self.request.POST.get('del_auth_token')
205 del_auth_token = self.request.POST.get('del_auth_token')
206
206
207 if del_auth_token:
207 if del_auth_token:
208 token = UserApiKeys.get_or_404(del_auth_token)
208 token = UserApiKeys.get_or_404(del_auth_token)
209 token_data = token.get_api_data()
209 token_data = token.get_api_data()
210
210
211 AuthTokenModel().delete(del_auth_token, c.user.user_id)
211 AuthTokenModel().delete(del_auth_token, c.user.user_id)
212 audit_logger.store_web(
212 audit_logger.store_web(
213 'user.edit.token.delete', action_data={
213 'user.edit.token.delete', action_data={
214 'data': {'token': token_data, 'user': 'self'}},
214 'data': {'token': token_data, 'user': 'self'}},
215 user=self._rhodecode_user,)
215 user=self._rhodecode_user,)
216 Session().commit()
216 Session().commit()
217 h.flash(_("Auth token successfully deleted"), category='success')
217 h.flash(_("Auth token successfully deleted"), category='success')
218
218
219 return HTTPFound(h.route_path('my_account_auth_tokens'))
219 return HTTPFound(h.route_path('my_account_auth_tokens'))
220
220
221 @LoginRequired()
221 @LoginRequired()
222 @NotAnonymous()
222 @NotAnonymous()
223 @view_config(
223 @view_config(
224 route_name='my_account_emails', request_method='GET',
224 route_name='my_account_emails', request_method='GET',
225 renderer='rhodecode:templates/admin/my_account/my_account.mako')
225 renderer='rhodecode:templates/admin/my_account/my_account.mako')
226 def my_account_emails(self):
226 def my_account_emails(self):
227 _ = self.request.translate
227 _ = self.request.translate
228
228
229 c = self.load_default_context()
229 c = self.load_default_context()
230 c.active = 'emails'
230 c.active = 'emails'
231
231
232 c.user_email_map = UserEmailMap.query()\
232 c.user_email_map = UserEmailMap.query()\
233 .filter(UserEmailMap.user == c.user).all()
233 .filter(UserEmailMap.user == c.user).all()
234 return self._get_template_context(c)
234 return self._get_template_context(c)
235
235
236 @LoginRequired()
236 @LoginRequired()
237 @NotAnonymous()
237 @NotAnonymous()
238 @CSRFRequired()
238 @CSRFRequired()
239 @view_config(
239 @view_config(
240 route_name='my_account_emails_add', request_method='POST')
240 route_name='my_account_emails_add', request_method='POST')
241 def my_account_emails_add(self):
241 def my_account_emails_add(self):
242 _ = self.request.translate
242 _ = self.request.translate
243 c = self.load_default_context()
243 c = self.load_default_context()
244
244
245 email = self.request.POST.get('new_email')
245 email = self.request.POST.get('new_email')
246
246
247 try:
247 try:
248 form = UserExtraEmailForm(self.request.translate)()
249 data = form.to_python({'email': email})
250 email = data['email']
251
248 UserModel().add_extra_email(c.user.user_id, email)
252 UserModel().add_extra_email(c.user.user_id, email)
249 audit_logger.store_web(
253 audit_logger.store_web(
250 'user.edit.email.add', action_data={
254 'user.edit.email.add', action_data={
251 'data': {'email': email, 'user': 'self'}},
255 'data': {'email': email, 'user': 'self'}},
252 user=self._rhodecode_user,)
256 user=self._rhodecode_user,)
253
257
254 Session().commit()
258 Session().commit()
255 h.flash(_("Added new email address `%s` for user account") % email,
259 h.flash(_("Added new email address `%s` for user account") % email,
256 category='success')
260 category='success')
257 except formencode.Invalid as error:
261 except formencode.Invalid as error:
258 h.flash(h.escape(error.error_dict['email']), category='error')
262 h.flash(h.escape(error.error_dict['email']), category='error')
259 except Exception:
263 except Exception:
260 log.exception("Exception in my_account_emails")
264 log.exception("Exception in my_account_emails")
261 h.flash(_('An error occurred during email saving'),
265 h.flash(_('An error occurred during email saving'),
262 category='error')
266 category='error')
263 return HTTPFound(h.route_path('my_account_emails'))
267 return HTTPFound(h.route_path('my_account_emails'))
264
268
265 @LoginRequired()
269 @LoginRequired()
266 @NotAnonymous()
270 @NotAnonymous()
267 @CSRFRequired()
271 @CSRFRequired()
268 @view_config(
272 @view_config(
269 route_name='my_account_emails_delete', request_method='POST')
273 route_name='my_account_emails_delete', request_method='POST')
270 def my_account_emails_delete(self):
274 def my_account_emails_delete(self):
271 _ = self.request.translate
275 _ = self.request.translate
272 c = self.load_default_context()
276 c = self.load_default_context()
273
277
274 del_email_id = self.request.POST.get('del_email_id')
278 del_email_id = self.request.POST.get('del_email_id')
275 if del_email_id:
279 if del_email_id:
276 email = UserEmailMap.get_or_404(del_email_id).email
280 email = UserEmailMap.get_or_404(del_email_id).email
277 UserModel().delete_extra_email(c.user.user_id, del_email_id)
281 UserModel().delete_extra_email(c.user.user_id, del_email_id)
278 audit_logger.store_web(
282 audit_logger.store_web(
279 'user.edit.email.delete', action_data={
283 'user.edit.email.delete', action_data={
280 'data': {'email': email, 'user': 'self'}},
284 'data': {'email': email, 'user': 'self'}},
281 user=self._rhodecode_user,)
285 user=self._rhodecode_user,)
282 Session().commit()
286 Session().commit()
283 h.flash(_("Email successfully deleted"),
287 h.flash(_("Email successfully deleted"),
284 category='success')
288 category='success')
285 return HTTPFound(h.route_path('my_account_emails'))
289 return HTTPFound(h.route_path('my_account_emails'))
286
290
287 @LoginRequired()
291 @LoginRequired()
288 @NotAnonymous()
292 @NotAnonymous()
289 @CSRFRequired()
293 @CSRFRequired()
290 @view_config(
294 @view_config(
291 route_name='my_account_notifications_test_channelstream',
295 route_name='my_account_notifications_test_channelstream',
292 request_method='POST', renderer='json_ext')
296 request_method='POST', renderer='json_ext')
293 def my_account_notifications_test_channelstream(self):
297 def my_account_notifications_test_channelstream(self):
294 message = 'Test message sent via Channelstream by user: {}, on {}'.format(
298 message = 'Test message sent via Channelstream by user: {}, on {}'.format(
295 self._rhodecode_user.username, datetime.datetime.now())
299 self._rhodecode_user.username, datetime.datetime.now())
296 payload = {
300 payload = {
297 # 'channel': 'broadcast',
301 # 'channel': 'broadcast',
298 'type': 'message',
302 'type': 'message',
299 'timestamp': datetime.datetime.utcnow(),
303 'timestamp': datetime.datetime.utcnow(),
300 'user': 'system',
304 'user': 'system',
301 'pm_users': [self._rhodecode_user.username],
305 'pm_users': [self._rhodecode_user.username],
302 'message': {
306 'message': {
303 'message': message,
307 'message': message,
304 'level': 'info',
308 'level': 'info',
305 'topic': '/notifications'
309 'topic': '/notifications'
306 }
310 }
307 }
311 }
308
312
309 registry = self.request.registry
313 registry = self.request.registry
310 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
314 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
311 channelstream_config = rhodecode_plugins.get('channelstream', {})
315 channelstream_config = rhodecode_plugins.get('channelstream', {})
312
316
313 try:
317 try:
314 channelstream_request(channelstream_config, [payload], '/message')
318 channelstream_request(channelstream_config, [payload], '/message')
315 except ChannelstreamException as e:
319 except ChannelstreamException as e:
316 log.exception('Failed to send channelstream data')
320 log.exception('Failed to send channelstream data')
317 return {"response": 'ERROR: {}'.format(e.__class__.__name__)}
321 return {"response": 'ERROR: {}'.format(e.__class__.__name__)}
318 return {"response": 'Channelstream data sent. '
322 return {"response": 'Channelstream data sent. '
319 'You should see a new live message now.'}
323 'You should see a new live message now.'}
320
324
321 def _load_my_repos_data(self, watched=False):
325 def _load_my_repos_data(self, watched=False):
322 if watched:
326 if watched:
323 admin = False
327 admin = False
324 follows_repos = Session().query(UserFollowing)\
328 follows_repos = Session().query(UserFollowing)\
325 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
329 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
326 .options(joinedload(UserFollowing.follows_repository))\
330 .options(joinedload(UserFollowing.follows_repository))\
327 .all()
331 .all()
328 repo_list = [x.follows_repository for x in follows_repos]
332 repo_list = [x.follows_repository for x in follows_repos]
329 else:
333 else:
330 admin = True
334 admin = True
331 repo_list = Repository.get_all_repos(
335 repo_list = Repository.get_all_repos(
332 user_id=self._rhodecode_user.user_id)
336 user_id=self._rhodecode_user.user_id)
333 repo_list = RepoList(repo_list, perm_set=[
337 repo_list = RepoList(repo_list, perm_set=[
334 'repository.read', 'repository.write', 'repository.admin'])
338 'repository.read', 'repository.write', 'repository.admin'])
335
339
336 repos_data = RepoModel().get_repos_as_dict(
340 repos_data = RepoModel().get_repos_as_dict(
337 repo_list=repo_list, admin=admin)
341 repo_list=repo_list, admin=admin)
338 # json used to render the grid
342 # json used to render the grid
339 return json.dumps(repos_data)
343 return json.dumps(repos_data)
340
344
341 @LoginRequired()
345 @LoginRequired()
342 @NotAnonymous()
346 @NotAnonymous()
343 @view_config(
347 @view_config(
344 route_name='my_account_repos', request_method='GET',
348 route_name='my_account_repos', request_method='GET',
345 renderer='rhodecode:templates/admin/my_account/my_account.mako')
349 renderer='rhodecode:templates/admin/my_account/my_account.mako')
346 def my_account_repos(self):
350 def my_account_repos(self):
347 c = self.load_default_context()
351 c = self.load_default_context()
348 c.active = 'repos'
352 c.active = 'repos'
349
353
350 # json used to render the grid
354 # json used to render the grid
351 c.data = self._load_my_repos_data()
355 c.data = self._load_my_repos_data()
352 return self._get_template_context(c)
356 return self._get_template_context(c)
353
357
354 @LoginRequired()
358 @LoginRequired()
355 @NotAnonymous()
359 @NotAnonymous()
356 @view_config(
360 @view_config(
357 route_name='my_account_watched', request_method='GET',
361 route_name='my_account_watched', request_method='GET',
358 renderer='rhodecode:templates/admin/my_account/my_account.mako')
362 renderer='rhodecode:templates/admin/my_account/my_account.mako')
359 def my_account_watched(self):
363 def my_account_watched(self):
360 c = self.load_default_context()
364 c = self.load_default_context()
361 c.active = 'watched'
365 c.active = 'watched'
362
366
363 # json used to render the grid
367 # json used to render the grid
364 c.data = self._load_my_repos_data(watched=True)
368 c.data = self._load_my_repos_data(watched=True)
365 return self._get_template_context(c)
369 return self._get_template_context(c)
366
370
367 @LoginRequired()
371 @LoginRequired()
368 @NotAnonymous()
372 @NotAnonymous()
369 @view_config(
373 @view_config(
370 route_name='my_account_perms', request_method='GET',
374 route_name='my_account_perms', request_method='GET',
371 renderer='rhodecode:templates/admin/my_account/my_account.mako')
375 renderer='rhodecode:templates/admin/my_account/my_account.mako')
372 def my_account_perms(self):
376 def my_account_perms(self):
373 c = self.load_default_context()
377 c = self.load_default_context()
374 c.active = 'perms'
378 c.active = 'perms'
375
379
376 c.perm_user = c.auth_user
380 c.perm_user = c.auth_user
377 return self._get_template_context(c)
381 return self._get_template_context(c)
378
382
379 @LoginRequired()
383 @LoginRequired()
380 @NotAnonymous()
384 @NotAnonymous()
381 @view_config(
385 @view_config(
382 route_name='my_account_notifications', request_method='GET',
386 route_name='my_account_notifications', request_method='GET',
383 renderer='rhodecode:templates/admin/my_account/my_account.mako')
387 renderer='rhodecode:templates/admin/my_account/my_account.mako')
384 def my_notifications(self):
388 def my_notifications(self):
385 c = self.load_default_context()
389 c = self.load_default_context()
386 c.active = 'notifications'
390 c.active = 'notifications'
387
391
388 return self._get_template_context(c)
392 return self._get_template_context(c)
389
393
390 @LoginRequired()
394 @LoginRequired()
391 @NotAnonymous()
395 @NotAnonymous()
392 @CSRFRequired()
396 @CSRFRequired()
393 @view_config(
397 @view_config(
394 route_name='my_account_notifications_toggle_visibility',
398 route_name='my_account_notifications_toggle_visibility',
395 request_method='POST', renderer='json_ext')
399 request_method='POST', renderer='json_ext')
396 def my_notifications_toggle_visibility(self):
400 def my_notifications_toggle_visibility(self):
397 user = self._rhodecode_db_user
401 user = self._rhodecode_db_user
398 new_status = not user.user_data.get('notification_status', True)
402 new_status = not user.user_data.get('notification_status', True)
399 user.update_userdata(notification_status=new_status)
403 user.update_userdata(notification_status=new_status)
400 Session().commit()
404 Session().commit()
401 return user.user_data['notification_status']
405 return user.user_data['notification_status']
402
406
403 @LoginRequired()
407 @LoginRequired()
404 @NotAnonymous()
408 @NotAnonymous()
405 @view_config(
409 @view_config(
406 route_name='my_account_edit',
410 route_name='my_account_edit',
407 request_method='GET',
411 request_method='GET',
408 renderer='rhodecode:templates/admin/my_account/my_account.mako')
412 renderer='rhodecode:templates/admin/my_account/my_account.mako')
409 def my_account_edit(self):
413 def my_account_edit(self):
410 c = self.load_default_context()
414 c = self.load_default_context()
411 c.active = 'profile_edit'
415 c.active = 'profile_edit'
412
416
413 c.perm_user = c.auth_user
417 c.perm_user = c.auth_user
414 c.extern_type = c.user.extern_type
418 c.extern_type = c.user.extern_type
415 c.extern_name = c.user.extern_name
419 c.extern_name = c.user.extern_name
416
420
417 defaults = c.user.get_dict()
421 defaults = c.user.get_dict()
418
422
419 data = render('rhodecode:templates/admin/my_account/my_account.mako',
423 data = render('rhodecode:templates/admin/my_account/my_account.mako',
420 self._get_template_context(c), self.request)
424 self._get_template_context(c), self.request)
421 html = formencode.htmlfill.render(
425 html = formencode.htmlfill.render(
422 data,
426 data,
423 defaults=defaults,
427 defaults=defaults,
424 encoding="UTF-8",
428 encoding="UTF-8",
425 force_defaults=False
429 force_defaults=False
426 )
430 )
427 return Response(html)
431 return Response(html)
428
432
429 @LoginRequired()
433 @LoginRequired()
430 @NotAnonymous()
434 @NotAnonymous()
431 @CSRFRequired()
435 @CSRFRequired()
432 @view_config(
436 @view_config(
433 route_name='my_account_update',
437 route_name='my_account_update',
434 request_method='POST',
438 request_method='POST',
435 renderer='rhodecode:templates/admin/my_account/my_account.mako')
439 renderer='rhodecode:templates/admin/my_account/my_account.mako')
436 def my_account_update(self):
440 def my_account_update(self):
437 _ = self.request.translate
441 _ = self.request.translate
438 c = self.load_default_context()
442 c = self.load_default_context()
439 c.active = 'profile_edit'
443 c.active = 'profile_edit'
440
444
441 c.perm_user = c.auth_user
445 c.perm_user = c.auth_user
442 c.extern_type = c.user.extern_type
446 c.extern_type = c.user.extern_type
443 c.extern_name = c.user.extern_name
447 c.extern_name = c.user.extern_name
444
448
445 _form = UserForm(edit=True,
449 _form = UserForm(self.request.translate, edit=True,
446 old_data={'user_id': self._rhodecode_user.user_id,
450 old_data={'user_id': self._rhodecode_user.user_id,
447 'email': self._rhodecode_user.email})()
451 'email': self._rhodecode_user.email})()
448 form_result = {}
452 form_result = {}
449 try:
453 try:
450 post_data = dict(self.request.POST)
454 post_data = dict(self.request.POST)
451 post_data['new_password'] = ''
455 post_data['new_password'] = ''
452 post_data['password_confirmation'] = ''
456 post_data['password_confirmation'] = ''
453 form_result = _form.to_python(post_data)
457 form_result = _form.to_python(post_data)
454 # skip updating those attrs for my account
458 # skip updating those attrs for my account
455 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
459 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
456 'new_password', 'password_confirmation']
460 'new_password', 'password_confirmation']
457 # TODO: plugin should define if username can be updated
461 # TODO: plugin should define if username can be updated
458 if c.extern_type != "rhodecode":
462 if c.extern_type != "rhodecode":
459 # forbid updating username for external accounts
463 # forbid updating username for external accounts
460 skip_attrs.append('username')
464 skip_attrs.append('username')
461
465
462 UserModel().update_user(
466 UserModel().update_user(
463 self._rhodecode_user.user_id, skip_attrs=skip_attrs,
467 self._rhodecode_user.user_id, skip_attrs=skip_attrs,
464 **form_result)
468 **form_result)
465 h.flash(_('Your account was updated successfully'),
469 h.flash(_('Your account was updated successfully'),
466 category='success')
470 category='success')
467 Session().commit()
471 Session().commit()
468
472
469 except formencode.Invalid as errors:
473 except formencode.Invalid as errors:
470 data = render(
474 data = render(
471 'rhodecode:templates/admin/my_account/my_account.mako',
475 'rhodecode:templates/admin/my_account/my_account.mako',
472 self._get_template_context(c), self.request)
476 self._get_template_context(c), self.request)
473
477
474 html = formencode.htmlfill.render(
478 html = formencode.htmlfill.render(
475 data,
479 data,
476 defaults=errors.value,
480 defaults=errors.value,
477 errors=errors.error_dict or {},
481 errors=errors.error_dict or {},
478 prefix_error=False,
482 prefix_error=False,
479 encoding="UTF-8",
483 encoding="UTF-8",
480 force_defaults=False)
484 force_defaults=False)
481 return Response(html)
485 return Response(html)
482
486
483 except Exception:
487 except Exception:
484 log.exception("Exception updating user")
488 log.exception("Exception updating user")
485 h.flash(_('Error occurred during update of user %s')
489 h.flash(_('Error occurred during update of user %s')
486 % form_result.get('username'), category='error')
490 % form_result.get('username'), category='error')
487 raise HTTPFound(h.route_path('my_account_profile'))
491 raise HTTPFound(h.route_path('my_account_profile'))
488
492
489 raise HTTPFound(h.route_path('my_account_profile'))
493 raise HTTPFound(h.route_path('my_account_profile'))
490
494
491 def _get_pull_requests_list(self, statuses):
495 def _get_pull_requests_list(self, statuses):
492 draw, start, limit = self._extract_chunk(self.request)
496 draw, start, limit = self._extract_chunk(self.request)
493 search_q, order_by, order_dir = self._extract_ordering(self.request)
497 search_q, order_by, order_dir = self._extract_ordering(self.request)
494 _render = self.request.get_partial_renderer(
498 _render = self.request.get_partial_renderer(
495 'rhodecode:templates/data_table/_dt_elements.mako')
499 'rhodecode:templates/data_table/_dt_elements.mako')
496
500
497 pull_requests = PullRequestModel().get_im_participating_in(
501 pull_requests = PullRequestModel().get_im_participating_in(
498 user_id=self._rhodecode_user.user_id,
502 user_id=self._rhodecode_user.user_id,
499 statuses=statuses,
503 statuses=statuses,
500 offset=start, length=limit, order_by=order_by,
504 offset=start, length=limit, order_by=order_by,
501 order_dir=order_dir)
505 order_dir=order_dir)
502
506
503 pull_requests_total_count = PullRequestModel().count_im_participating_in(
507 pull_requests_total_count = PullRequestModel().count_im_participating_in(
504 user_id=self._rhodecode_user.user_id, statuses=statuses)
508 user_id=self._rhodecode_user.user_id, statuses=statuses)
505
509
506 data = []
510 data = []
507 comments_model = CommentsModel()
511 comments_model = CommentsModel()
508 for pr in pull_requests:
512 for pr in pull_requests:
509 repo_id = pr.target_repo_id
513 repo_id = pr.target_repo_id
510 comments = comments_model.get_all_comments(
514 comments = comments_model.get_all_comments(
511 repo_id, pull_request=pr)
515 repo_id, pull_request=pr)
512 owned = pr.user_id == self._rhodecode_user.user_id
516 owned = pr.user_id == self._rhodecode_user.user_id
513
517
514 data.append({
518 data.append({
515 'target_repo': _render('pullrequest_target_repo',
519 'target_repo': _render('pullrequest_target_repo',
516 pr.target_repo.repo_name),
520 pr.target_repo.repo_name),
517 'name': _render('pullrequest_name',
521 'name': _render('pullrequest_name',
518 pr.pull_request_id, pr.target_repo.repo_name,
522 pr.pull_request_id, pr.target_repo.repo_name,
519 short=True),
523 short=True),
520 'name_raw': pr.pull_request_id,
524 'name_raw': pr.pull_request_id,
521 'status': _render('pullrequest_status',
525 'status': _render('pullrequest_status',
522 pr.calculated_review_status()),
526 pr.calculated_review_status()),
523 'title': _render(
527 'title': _render(
524 'pullrequest_title', pr.title, pr.description),
528 'pullrequest_title', pr.title, pr.description),
525 'description': h.escape(pr.description),
529 'description': h.escape(pr.description),
526 'updated_on': _render('pullrequest_updated_on',
530 'updated_on': _render('pullrequest_updated_on',
527 h.datetime_to_time(pr.updated_on)),
531 h.datetime_to_time(pr.updated_on)),
528 'updated_on_raw': h.datetime_to_time(pr.updated_on),
532 'updated_on_raw': h.datetime_to_time(pr.updated_on),
529 'created_on': _render('pullrequest_updated_on',
533 'created_on': _render('pullrequest_updated_on',
530 h.datetime_to_time(pr.created_on)),
534 h.datetime_to_time(pr.created_on)),
531 'created_on_raw': h.datetime_to_time(pr.created_on),
535 'created_on_raw': h.datetime_to_time(pr.created_on),
532 'author': _render('pullrequest_author',
536 'author': _render('pullrequest_author',
533 pr.author.full_contact, ),
537 pr.author.full_contact, ),
534 'author_raw': pr.author.full_name,
538 'author_raw': pr.author.full_name,
535 'comments': _render('pullrequest_comments', len(comments)),
539 'comments': _render('pullrequest_comments', len(comments)),
536 'comments_raw': len(comments),
540 'comments_raw': len(comments),
537 'closed': pr.is_closed(),
541 'closed': pr.is_closed(),
538 'owned': owned
542 'owned': owned
539 })
543 })
540
544
541 # json used to render the grid
545 # json used to render the grid
542 data = ({
546 data = ({
543 'draw': draw,
547 'draw': draw,
544 'data': data,
548 'data': data,
545 'recordsTotal': pull_requests_total_count,
549 'recordsTotal': pull_requests_total_count,
546 'recordsFiltered': pull_requests_total_count,
550 'recordsFiltered': pull_requests_total_count,
547 })
551 })
548 return data
552 return data
549
553
550 @LoginRequired()
554 @LoginRequired()
551 @NotAnonymous()
555 @NotAnonymous()
552 @view_config(
556 @view_config(
553 route_name='my_account_pullrequests',
557 route_name='my_account_pullrequests',
554 request_method='GET',
558 request_method='GET',
555 renderer='rhodecode:templates/admin/my_account/my_account.mako')
559 renderer='rhodecode:templates/admin/my_account/my_account.mako')
556 def my_account_pullrequests(self):
560 def my_account_pullrequests(self):
557 c = self.load_default_context()
561 c = self.load_default_context()
558 c.active = 'pullrequests'
562 c.active = 'pullrequests'
559 req_get = self.request.GET
563 req_get = self.request.GET
560
564
561 c.closed = str2bool(req_get.get('pr_show_closed'))
565 c.closed = str2bool(req_get.get('pr_show_closed'))
562
566
563 return self._get_template_context(c)
567 return self._get_template_context(c)
564
568
565 @LoginRequired()
569 @LoginRequired()
566 @NotAnonymous()
570 @NotAnonymous()
567 @view_config(
571 @view_config(
568 route_name='my_account_pullrequests_data',
572 route_name='my_account_pullrequests_data',
569 request_method='GET', renderer='json_ext')
573 request_method='GET', renderer='json_ext')
570 def my_account_pullrequests_data(self):
574 def my_account_pullrequests_data(self):
571 req_get = self.request.GET
575 req_get = self.request.GET
572 closed = str2bool(req_get.get('closed'))
576 closed = str2bool(req_get.get('closed'))
573
577
574 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
578 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
575 if closed:
579 if closed:
576 statuses += [PullRequest.STATUS_CLOSED]
580 statuses += [PullRequest.STATUS_CLOSED]
577
581
578 data = self._get_pull_requests_list(statuses=statuses)
582 data = self._get_pull_requests_list(statuses=statuses)
579 return data
583 return data
580
584
@@ -1,198 +1,198 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22
22
23 from pyramid.httpexceptions import (
23 from pyramid.httpexceptions import (
24 HTTPFound, HTTPNotFound, HTTPInternalServerError)
24 HTTPFound, HTTPNotFound, HTTPInternalServerError)
25 from pyramid.view import view_config
25 from pyramid.view import view_config
26
26
27 from rhodecode.apps._base import BaseAppView
27 from rhodecode.apps._base import BaseAppView
28 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
28 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
29
29
30 from rhodecode.lib import helpers as h
30 from rhodecode.lib import helpers as h
31 from rhodecode.lib.helpers import Page
31 from rhodecode.lib.helpers import Page
32 from rhodecode.lib.utils2 import safe_int
32 from rhodecode.lib.utils2 import safe_int
33 from rhodecode.model.db import Notification
33 from rhodecode.model.db import Notification
34 from rhodecode.model.notification import NotificationModel
34 from rhodecode.model.notification import NotificationModel
35 from rhodecode.model.meta import Session
35 from rhodecode.model.meta import Session
36
36
37
37
38 log = logging.getLogger(__name__)
38 log = logging.getLogger(__name__)
39
39
40
40
41 class MyAccountNotificationsView(BaseAppView):
41 class MyAccountNotificationsView(BaseAppView):
42
42
43 def load_default_context(self):
43 def load_default_context(self):
44 c = self._get_local_tmpl_context()
44 c = self._get_local_tmpl_context()
45 c.user = c.auth_user.get_instance()
45 c.user = c.auth_user.get_instance()
46 self._register_global_c(c)
46
47 return c
47 return c
48
48
49 def _has_permissions(self, notification):
49 def _has_permissions(self, notification):
50 def is_owner():
50 def is_owner():
51 user_id = self._rhodecode_db_user.user_id
51 user_id = self._rhodecode_db_user.user_id
52 for user_notification in notification.notifications_to_users:
52 for user_notification in notification.notifications_to_users:
53 if user_notification.user.user_id == user_id:
53 if user_notification.user.user_id == user_id:
54 return True
54 return True
55 return False
55 return False
56 return h.HasPermissionAny('hg.admin')() or is_owner()
56 return h.HasPermissionAny('hg.admin')() or is_owner()
57
57
58 @LoginRequired()
58 @LoginRequired()
59 @NotAnonymous()
59 @NotAnonymous()
60 @view_config(
60 @view_config(
61 route_name='notifications_show_all', request_method='GET',
61 route_name='notifications_show_all', request_method='GET',
62 renderer='rhodecode:templates/admin/notifications/notifications_show_all.mako')
62 renderer='rhodecode:templates/admin/notifications/notifications_show_all.mako')
63 def notifications_show_all(self):
63 def notifications_show_all(self):
64 c = self.load_default_context()
64 c = self.load_default_context()
65
65
66 c.unread_count = NotificationModel().get_unread_cnt_for_user(
66 c.unread_count = NotificationModel().get_unread_cnt_for_user(
67 self._rhodecode_db_user.user_id)
67 self._rhodecode_db_user.user_id)
68
68
69 _current_filter = self.request.GET.getall('type') or ['unread']
69 _current_filter = self.request.GET.getall('type') or ['unread']
70
70
71 notifications = NotificationModel().get_for_user(
71 notifications = NotificationModel().get_for_user(
72 self._rhodecode_db_user.user_id,
72 self._rhodecode_db_user.user_id,
73 filter_=_current_filter)
73 filter_=_current_filter)
74
74
75 p = safe_int(self.request.GET.get('page', 1), 1)
75 p = safe_int(self.request.GET.get('page', 1), 1)
76
76
77 def url_generator(**kw):
77 def url_generator(**kw):
78 _query = self.request.GET.mixed()
78 _query = self.request.GET.mixed()
79 _query.update(kw)
79 _query.update(kw)
80 return self.request.current_route_path(_query=_query)
80 return self.request.current_route_path(_query=_query)
81
81
82 c.notifications = Page(notifications, page=p, items_per_page=10,
82 c.notifications = Page(notifications, page=p, items_per_page=10,
83 url=url_generator)
83 url=url_generator)
84
84
85 c.unread_type = 'unread'
85 c.unread_type = 'unread'
86 c.all_type = 'all'
86 c.all_type = 'all'
87 c.pull_request_type = Notification.TYPE_PULL_REQUEST
87 c.pull_request_type = Notification.TYPE_PULL_REQUEST
88 c.comment_type = [Notification.TYPE_CHANGESET_COMMENT,
88 c.comment_type = [Notification.TYPE_CHANGESET_COMMENT,
89 Notification.TYPE_PULL_REQUEST_COMMENT]
89 Notification.TYPE_PULL_REQUEST_COMMENT]
90
90
91 c.current_filter = 'unread' # default filter
91 c.current_filter = 'unread' # default filter
92
92
93 if _current_filter == [c.pull_request_type]:
93 if _current_filter == [c.pull_request_type]:
94 c.current_filter = 'pull_request'
94 c.current_filter = 'pull_request'
95 elif _current_filter == c.comment_type:
95 elif _current_filter == c.comment_type:
96 c.current_filter = 'comment'
96 c.current_filter = 'comment'
97 elif _current_filter == [c.unread_type]:
97 elif _current_filter == [c.unread_type]:
98 c.current_filter = 'unread'
98 c.current_filter = 'unread'
99 elif _current_filter == [c.all_type]:
99 elif _current_filter == [c.all_type]:
100 c.current_filter = 'all'
100 c.current_filter = 'all'
101 return self._get_template_context(c)
101 return self._get_template_context(c)
102
102
103 @LoginRequired()
103 @LoginRequired()
104 @NotAnonymous()
104 @NotAnonymous()
105 @CSRFRequired()
105 @CSRFRequired()
106 @view_config(
106 @view_config(
107 route_name='notifications_mark_all_read', request_method='POST',
107 route_name='notifications_mark_all_read', request_method='POST',
108 renderer='rhodecode:templates/admin/notifications/notifications_show_all.mako')
108 renderer='rhodecode:templates/admin/notifications/notifications_show_all.mako')
109 def notifications_mark_all_read(self):
109 def notifications_mark_all_read(self):
110 NotificationModel().mark_all_read_for_user(
110 NotificationModel().mark_all_read_for_user(
111 self._rhodecode_db_user.user_id,
111 self._rhodecode_db_user.user_id,
112 filter_=self.request.GET.getall('type'))
112 filter_=self.request.GET.getall('type'))
113 Session().commit()
113 Session().commit()
114 raise HTTPFound(h.route_path('notifications_show_all'))
114 raise HTTPFound(h.route_path('notifications_show_all'))
115
115
116 @LoginRequired()
116 @LoginRequired()
117 @NotAnonymous()
117 @NotAnonymous()
118 @view_config(
118 @view_config(
119 route_name='notifications_show', request_method='GET',
119 route_name='notifications_show', request_method='GET',
120 renderer='rhodecode:templates/admin/notifications/notifications_show.mako')
120 renderer='rhodecode:templates/admin/notifications/notifications_show.mako')
121 def notifications_show(self):
121 def notifications_show(self):
122 c = self.load_default_context()
122 c = self.load_default_context()
123 notification_id = self.request.matchdict['notification_id']
123 notification_id = self.request.matchdict['notification_id']
124 notification = Notification.get_or_404(notification_id)
124 notification = Notification.get_or_404(notification_id)
125
125
126 if not self._has_permissions(notification):
126 if not self._has_permissions(notification):
127 log.debug('User %s does not have permission to access notification',
127 log.debug('User %s does not have permission to access notification',
128 self._rhodecode_user)
128 self._rhodecode_user)
129 raise HTTPNotFound()
129 raise HTTPNotFound()
130
130
131 u_notification = NotificationModel().get_user_notification(
131 u_notification = NotificationModel().get_user_notification(
132 self._rhodecode_db_user.user_id, notification)
132 self._rhodecode_db_user.user_id, notification)
133 if not u_notification:
133 if not u_notification:
134 log.debug('User %s notification does not exist',
134 log.debug('User %s notification does not exist',
135 self._rhodecode_user)
135 self._rhodecode_user)
136 raise HTTPNotFound()
136 raise HTTPNotFound()
137
137
138 # when opening this notification, mark it as read for this use
138 # when opening this notification, mark it as read for this use
139 if not u_notification.read:
139 if not u_notification.read:
140 u_notification.mark_as_read()
140 u_notification.mark_as_read()
141 Session().commit()
141 Session().commit()
142
142
143 c.notification = notification
143 c.notification = notification
144
144
145 return self._get_template_context(c)
145 return self._get_template_context(c)
146
146
147 @LoginRequired()
147 @LoginRequired()
148 @NotAnonymous()
148 @NotAnonymous()
149 @CSRFRequired()
149 @CSRFRequired()
150 @view_config(
150 @view_config(
151 route_name='notifications_update', request_method='POST',
151 route_name='notifications_update', request_method='POST',
152 renderer='json_ext')
152 renderer='json_ext')
153 def notification_update(self):
153 def notification_update(self):
154 notification_id = self.request.matchdict['notification_id']
154 notification_id = self.request.matchdict['notification_id']
155 notification = Notification.get_or_404(notification_id)
155 notification = Notification.get_or_404(notification_id)
156
156
157 if not self._has_permissions(notification):
157 if not self._has_permissions(notification):
158 log.debug('User %s does not have permission to access notification',
158 log.debug('User %s does not have permission to access notification',
159 self._rhodecode_user)
159 self._rhodecode_user)
160 raise HTTPNotFound()
160 raise HTTPNotFound()
161
161
162 try:
162 try:
163 # updates notification read flag
163 # updates notification read flag
164 NotificationModel().mark_read(
164 NotificationModel().mark_read(
165 self._rhodecode_user.user_id, notification)
165 self._rhodecode_user.user_id, notification)
166 Session().commit()
166 Session().commit()
167 return 'ok'
167 return 'ok'
168 except Exception:
168 except Exception:
169 Session().rollback()
169 Session().rollback()
170 log.exception("Exception updating a notification item")
170 log.exception("Exception updating a notification item")
171
171
172 raise HTTPInternalServerError()
172 raise HTTPInternalServerError()
173
173
174 @LoginRequired()
174 @LoginRequired()
175 @NotAnonymous()
175 @NotAnonymous()
176 @CSRFRequired()
176 @CSRFRequired()
177 @view_config(
177 @view_config(
178 route_name='notifications_delete', request_method='POST',
178 route_name='notifications_delete', request_method='POST',
179 renderer='json_ext')
179 renderer='json_ext')
180 def notification_delete(self):
180 def notification_delete(self):
181 notification_id = self.request.matchdict['notification_id']
181 notification_id = self.request.matchdict['notification_id']
182 notification = Notification.get_or_404(notification_id)
182 notification = Notification.get_or_404(notification_id)
183 if not self._has_permissions(notification):
183 if not self._has_permissions(notification):
184 log.debug('User %s does not have permission to access notification',
184 log.debug('User %s does not have permission to access notification',
185 self._rhodecode_user)
185 self._rhodecode_user)
186 raise HTTPNotFound()
186 raise HTTPNotFound()
187
187
188 try:
188 try:
189 # deletes only notification2user
189 # deletes only notification2user
190 NotificationModel().delete(
190 NotificationModel().delete(
191 self._rhodecode_user.user_id, notification)
191 self._rhodecode_user.user_id, notification)
192 Session().commit()
192 Session().commit()
193 return 'ok'
193 return 'ok'
194 except Exception:
194 except Exception:
195 Session().rollback()
195 Session().rollback()
196 log.exception("Exception deleting a notification item")
196 log.exception("Exception deleting a notification item")
197
197
198 raise HTTPInternalServerError()
198 raise HTTPInternalServerError()
@@ -1,154 +1,154 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22
22
23 from pyramid.httpexceptions import HTTPFound
23 from pyramid.httpexceptions import HTTPFound
24 from pyramid.view import view_config
24 from pyramid.view import view_config
25
25
26 from rhodecode.apps._base import BaseAppView, DataGridAppView
26 from rhodecode.apps._base import BaseAppView, DataGridAppView
27 from rhodecode.apps.ssh_support import SshKeyFileChangeEvent
27 from rhodecode.apps.ssh_support import SshKeyFileChangeEvent
28 from rhodecode.events import trigger
28 from rhodecode.events import trigger
29 from rhodecode.lib import helpers as h
29 from rhodecode.lib import helpers as h
30 from rhodecode.lib import audit_logger
30 from rhodecode.lib import audit_logger
31 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
31 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
32 from rhodecode.model.db import IntegrityError, UserSshKeys
32 from rhodecode.model.db import IntegrityError, UserSshKeys
33 from rhodecode.model.meta import Session
33 from rhodecode.model.meta import Session
34 from rhodecode.model.ssh_key import SshKeyModel
34 from rhodecode.model.ssh_key import SshKeyModel
35
35
36 log = logging.getLogger(__name__)
36 log = logging.getLogger(__name__)
37
37
38
38
39 class MyAccountSshKeysView(BaseAppView, DataGridAppView):
39 class MyAccountSshKeysView(BaseAppView, DataGridAppView):
40
40
41 def load_default_context(self):
41 def load_default_context(self):
42 c = self._get_local_tmpl_context()
42 c = self._get_local_tmpl_context()
43 c.user = c.auth_user.get_instance()
43 c.user = c.auth_user.get_instance()
44
44
45 c.ssh_enabled = self.request.registry.settings.get(
45 c.ssh_enabled = self.request.registry.settings.get(
46 'ssh.generate_authorized_keyfile')
46 'ssh.generate_authorized_keyfile')
47 self._register_global_c(c)
47
48 return c
48 return c
49
49
50 @LoginRequired()
50 @LoginRequired()
51 @NotAnonymous()
51 @NotAnonymous()
52 @view_config(
52 @view_config(
53 route_name='my_account_ssh_keys', request_method='GET',
53 route_name='my_account_ssh_keys', request_method='GET',
54 renderer='rhodecode:templates/admin/my_account/my_account.mako')
54 renderer='rhodecode:templates/admin/my_account/my_account.mako')
55 def my_account_ssh_keys(self):
55 def my_account_ssh_keys(self):
56 _ = self.request.translate
56 _ = self.request.translate
57
57
58 c = self.load_default_context()
58 c = self.load_default_context()
59 c.active = 'ssh_keys'
59 c.active = 'ssh_keys'
60 c.default_key = self.request.GET.get('default_key')
60 c.default_key = self.request.GET.get('default_key')
61 c.user_ssh_keys = SshKeyModel().get_ssh_keys(c.user.user_id)
61 c.user_ssh_keys = SshKeyModel().get_ssh_keys(c.user.user_id)
62 return self._get_template_context(c)
62 return self._get_template_context(c)
63
63
64 @LoginRequired()
64 @LoginRequired()
65 @NotAnonymous()
65 @NotAnonymous()
66 @view_config(
66 @view_config(
67 route_name='my_account_ssh_keys_generate', request_method='GET',
67 route_name='my_account_ssh_keys_generate', request_method='GET',
68 renderer='rhodecode:templates/admin/my_account/my_account.mako')
68 renderer='rhodecode:templates/admin/my_account/my_account.mako')
69 def ssh_keys_generate_keypair(self):
69 def ssh_keys_generate_keypair(self):
70 _ = self.request.translate
70 _ = self.request.translate
71 c = self.load_default_context()
71 c = self.load_default_context()
72
72
73 c.active = 'ssh_keys_generate'
73 c.active = 'ssh_keys_generate'
74 comment = 'RhodeCode-SSH {}'.format(c.user.email or '')
74 comment = 'RhodeCode-SSH {}'.format(c.user.email or '')
75 c.private, c.public = SshKeyModel().generate_keypair(comment=comment)
75 c.private, c.public = SshKeyModel().generate_keypair(comment=comment)
76 c.target_form_url = h.route_path(
76 c.target_form_url = h.route_path(
77 'my_account_ssh_keys', _query=dict(default_key=c.public))
77 'my_account_ssh_keys', _query=dict(default_key=c.public))
78 return self._get_template_context(c)
78 return self._get_template_context(c)
79
79
80 @LoginRequired()
80 @LoginRequired()
81 @NotAnonymous()
81 @NotAnonymous()
82 @CSRFRequired()
82 @CSRFRequired()
83 @view_config(
83 @view_config(
84 route_name='my_account_ssh_keys_add', request_method='POST',)
84 route_name='my_account_ssh_keys_add', request_method='POST',)
85 def my_account_ssh_keys_add(self):
85 def my_account_ssh_keys_add(self):
86 _ = self.request.translate
86 _ = self.request.translate
87 c = self.load_default_context()
87 c = self.load_default_context()
88
88
89 user_data = c.user.get_api_data()
89 user_data = c.user.get_api_data()
90 key_data = self.request.POST.get('key_data')
90 key_data = self.request.POST.get('key_data')
91 description = self.request.POST.get('description')
91 description = self.request.POST.get('description')
92
92
93 try:
93 try:
94 if not key_data:
94 if not key_data:
95 raise ValueError('Please add a valid public key')
95 raise ValueError('Please add a valid public key')
96
96
97 key = SshKeyModel().parse_key(key_data.strip())
97 key = SshKeyModel().parse_key(key_data.strip())
98 fingerprint = key.hash_md5()
98 fingerprint = key.hash_md5()
99
99
100 ssh_key = SshKeyModel().create(
100 ssh_key = SshKeyModel().create(
101 c.user.user_id, fingerprint, key_data, description)
101 c.user.user_id, fingerprint, key_data, description)
102 ssh_key_data = ssh_key.get_api_data()
102 ssh_key_data = ssh_key.get_api_data()
103
103
104 audit_logger.store_web(
104 audit_logger.store_web(
105 'user.edit.ssh_key.add', action_data={
105 'user.edit.ssh_key.add', action_data={
106 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
106 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
107 user=self._rhodecode_user, )
107 user=self._rhodecode_user, )
108 Session().commit()
108 Session().commit()
109
109
110 # Trigger an event on change of keys.
110 # Trigger an event on change of keys.
111 trigger(SshKeyFileChangeEvent(), self.request.registry)
111 trigger(SshKeyFileChangeEvent(), self.request.registry)
112
112
113 h.flash(_("Ssh Key successfully created"), category='success')
113 h.flash(_("Ssh Key successfully created"), category='success')
114
114
115 except IntegrityError:
115 except IntegrityError:
116 log.exception("Exception during ssh key saving")
116 log.exception("Exception during ssh key saving")
117 h.flash(_('An error occurred during ssh key saving: {}').format(
117 h.flash(_('An error occurred during ssh key saving: {}').format(
118 'Such key already exists, please use a different one'),
118 'Such key already exists, please use a different one'),
119 category='error')
119 category='error')
120 except Exception as e:
120 except Exception as e:
121 log.exception("Exception during ssh key saving")
121 log.exception("Exception during ssh key saving")
122 h.flash(_('An error occurred during ssh key saving: {}').format(e),
122 h.flash(_('An error occurred during ssh key saving: {}').format(e),
123 category='error')
123 category='error')
124
124
125 return HTTPFound(h.route_path('my_account_ssh_keys'))
125 return HTTPFound(h.route_path('my_account_ssh_keys'))
126
126
127 @LoginRequired()
127 @LoginRequired()
128 @NotAnonymous()
128 @NotAnonymous()
129 @CSRFRequired()
129 @CSRFRequired()
130 @view_config(
130 @view_config(
131 route_name='my_account_ssh_keys_delete', request_method='POST')
131 route_name='my_account_ssh_keys_delete', request_method='POST')
132 def my_account_ssh_keys_delete(self):
132 def my_account_ssh_keys_delete(self):
133 _ = self.request.translate
133 _ = self.request.translate
134 c = self.load_default_context()
134 c = self.load_default_context()
135
135
136 user_data = c.user.get_api_data()
136 user_data = c.user.get_api_data()
137
137
138 del_ssh_key = self.request.POST.get('del_ssh_key')
138 del_ssh_key = self.request.POST.get('del_ssh_key')
139
139
140 if del_ssh_key:
140 if del_ssh_key:
141 ssh_key = UserSshKeys.get_or_404(del_ssh_key)
141 ssh_key = UserSshKeys.get_or_404(del_ssh_key)
142 ssh_key_data = ssh_key.get_api_data()
142 ssh_key_data = ssh_key.get_api_data()
143
143
144 SshKeyModel().delete(del_ssh_key, c.user.user_id)
144 SshKeyModel().delete(del_ssh_key, c.user.user_id)
145 audit_logger.store_web(
145 audit_logger.store_web(
146 'user.edit.ssh_key.delete', action_data={
146 'user.edit.ssh_key.delete', action_data={
147 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
147 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
148 user=self._rhodecode_user,)
148 user=self._rhodecode_user,)
149 Session().commit()
149 Session().commit()
150 # Trigger an event on change of keys.
150 # Trigger an event on change of keys.
151 trigger(SshKeyFileChangeEvent(), self.request.registry)
151 trigger(SshKeyFileChangeEvent(), self.request.registry)
152 h.flash(_("Ssh key successfully deleted"), category='success')
152 h.flash(_("Ssh key successfully deleted"), category='success')
153
153
154 return HTTPFound(h.route_path('my_account_ssh_keys'))
154 return HTTPFound(h.route_path('my_account_ssh_keys'))
@@ -1,46 +1,46 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 from rhodecode.apps._base import ADMIN_PREFIX
21 from rhodecode.apps._base import ADMIN_PREFIX
22
22
23
23
24 def admin_routes(config):
24 def admin_routes(config):
25 config.add_route(
25 config.add_route(
26 name='ops_ping',
26 name='ops_ping',
27 pattern='/ping')
27 pattern='/ping')
28 config.add_route(
28 config.add_route(
29 name='ops_error_test',
29 name='ops_error_test',
30 pattern='/error')
30 pattern='/error')
31 config.add_route(
31 config.add_route(
32 name='ops_redirect_test',
32 name='ops_redirect_test',
33 pattern='/redirect')
33 pattern='/redirect')
34
34
35
35
36 def includeme(config):
36 def includeme(config):
37
37
38 config.include(admin_routes, route_prefix=ADMIN_PREFIX + '/ops')
38 config.include(admin_routes, route_prefix=ADMIN_PREFIX + '/ops')
39 # make OLD entries from pylons work
39 # make OLD entries from <4.10.0 work
40 config.add_route(
40 config.add_route(
41 name='ops_ping_legacy', pattern=ADMIN_PREFIX + '/ping')
41 name='ops_ping_legacy', pattern=ADMIN_PREFIX + '/ping')
42 config.add_route(
42 config.add_route(
43 name='ops_error_test_legacy', pattern=ADMIN_PREFIX + '/error_test')
43 name='ops_error_test_legacy', pattern=ADMIN_PREFIX + '/error_test')
44
44
45 # Scan module for configuration decorators.
45 # Scan module for configuration decorators.
46 config.scan('.views', ignore='.tests')
46 config.scan('.views', ignore='.tests')
@@ -1,83 +1,83 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import time
21 import time
22 import logging
22 import logging
23
23
24 from pyramid.view import view_config
24 from pyramid.view import view_config
25 from pyramid.httpexceptions import HTTPFound
25 from pyramid.httpexceptions import HTTPFound
26
26
27 from rhodecode.apps._base import BaseAppView
27 from rhodecode.apps._base import BaseAppView
28 from rhodecode.lib import helpers as h
28 from rhodecode.lib import helpers as h
29
29
30 log = logging.getLogger(__name__)
30 log = logging.getLogger(__name__)
31
31
32
32
33 class OpsView(BaseAppView):
33 class OpsView(BaseAppView):
34
34
35 def load_default_context(self):
35 def load_default_context(self):
36 c = self._get_local_tmpl_context()
36 c = self._get_local_tmpl_context()
37 c.user = c.auth_user.get_instance()
37 c.user = c.auth_user.get_instance()
38 self._register_global_c(c)
38
39 return c
39 return c
40
40
41 @view_config(
41 @view_config(
42 route_name='ops_ping', request_method='GET',
42 route_name='ops_ping', request_method='GET',
43 renderer='json_ext')
43 renderer='json_ext')
44 @view_config(
44 @view_config(
45 route_name='ops_ping_legacy', request_method='GET',
45 route_name='ops_ping_legacy', request_method='GET',
46 renderer='json_ext')
46 renderer='json_ext')
47 def ops_ping(self):
47 def ops_ping(self):
48 data = {
48 data = {
49 'instance': self.request.registry.settings.get('instance_id'),
49 'instance': self.request.registry.settings.get('instance_id'),
50 }
50 }
51 if getattr(self.request, 'user'):
51 if getattr(self.request, 'user'):
52 data.update({
52 data.update({
53 'caller_ip': self.request.user.ip_addr,
53 'caller_ip': self.request.user.ip_addr,
54 'caller_name': self.request.user.username,
54 'caller_name': self.request.user.username,
55 })
55 })
56 return {'ok': data}
56 return {'ok': data}
57
57
58 @view_config(
58 @view_config(
59 route_name='ops_error_test', request_method='GET',
59 route_name='ops_error_test', request_method='GET',
60 renderer='json_ext')
60 renderer='json_ext')
61 @view_config(
61 @view_config(
62 route_name='ops_error_test_legacy', request_method='GET',
62 route_name='ops_error_test_legacy', request_method='GET',
63 renderer='json_ext')
63 renderer='json_ext')
64 def ops_error_test(self):
64 def ops_error_test(self):
65 """
65 """
66 Test exception handling and emails on errors
66 Test exception handling and emails on errors
67 """
67 """
68 class TestException(Exception):
68 class TestException(Exception):
69 pass
69 pass
70
70
71 msg = ('RhodeCode Enterprise test exception. '
71 msg = ('RhodeCode Enterprise test exception. '
72 'Generation time: {}'.format(time.time()))
72 'Generation time: {}'.format(time.time()))
73 raise TestException(msg)
73 raise TestException(msg)
74
74
75 @view_config(
75 @view_config(
76 route_name='ops_redirect_test', request_method='GET',
76 route_name='ops_redirect_test', request_method='GET',
77 renderer='json_ext')
77 renderer='json_ext')
78 def ops_redirect_test(self):
78 def ops_redirect_test(self):
79 """
79 """
80 Test redirect handling
80 Test redirect handling
81 """
81 """
82 redirect_to = self.request.GET.get('to') or h.route_path('home')
82 redirect_to = self.request.GET.get('to') or h.route_path('home')
83 raise HTTPFound(redirect_to)
83 raise HTTPFound(redirect_to)
@@ -1,105 +1,105 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22
22
23 from pyramid.view import view_config
23 from pyramid.view import view_config
24 from pyramid.httpexceptions import HTTPFound
24 from pyramid.httpexceptions import HTTPFound
25
25
26 from rhodecode.apps._base import RepoGroupAppView
26 from rhodecode.apps._base import RepoGroupAppView
27 from rhodecode.lib import helpers as h
27 from rhodecode.lib import helpers as h
28 from rhodecode.lib import audit_logger
28 from rhodecode.lib import audit_logger
29 from rhodecode.lib.auth import (
29 from rhodecode.lib.auth import (
30 LoginRequired, CSRFRequired, HasRepoGroupPermissionAnyDecorator)
30 LoginRequired, CSRFRequired, HasRepoGroupPermissionAnyDecorator)
31 from rhodecode.model.repo_group import RepoGroupModel
31 from rhodecode.model.repo_group import RepoGroupModel
32 from rhodecode.model.meta import Session
32 from rhodecode.model.meta import Session
33
33
34 log = logging.getLogger(__name__)
34 log = logging.getLogger(__name__)
35
35
36
36
37 class RepoGroupSettingsView(RepoGroupAppView):
37 class RepoGroupSettingsView(RepoGroupAppView):
38 def load_default_context(self):
38 def load_default_context(self):
39 c = self._get_local_tmpl_context()
39 c = self._get_local_tmpl_context()
40 self._register_global_c(c)
40
41 return c
41 return c
42
42
43 @LoginRequired()
43 @LoginRequired()
44 @HasRepoGroupPermissionAnyDecorator('group.admin')
44 @HasRepoGroupPermissionAnyDecorator('group.admin')
45 @view_config(
45 @view_config(
46 route_name='edit_repo_group_advanced', request_method='GET',
46 route_name='edit_repo_group_advanced', request_method='GET',
47 renderer='rhodecode:templates/admin/repo_groups/repo_group_edit.mako')
47 renderer='rhodecode:templates/admin/repo_groups/repo_group_edit.mako')
48 def edit_repo_group_advanced(self):
48 def edit_repo_group_advanced(self):
49 c = self.load_default_context()
49 c = self.load_default_context()
50 c.active = 'advanced'
50 c.active = 'advanced'
51 c.repo_group = self.db_repo_group
51 c.repo_group = self.db_repo_group
52 return self._get_template_context(c)
52 return self._get_template_context(c)
53
53
54 @LoginRequired()
54 @LoginRequired()
55 @HasRepoGroupPermissionAnyDecorator('group.admin')
55 @HasRepoGroupPermissionAnyDecorator('group.admin')
56 @CSRFRequired()
56 @CSRFRequired()
57 @view_config(
57 @view_config(
58 route_name='edit_repo_group_advanced_delete', request_method='POST',
58 route_name='edit_repo_group_advanced_delete', request_method='POST',
59 renderer='rhodecode:templates/admin/repo_groups/repo_group_edit.mako')
59 renderer='rhodecode:templates/admin/repo_groups/repo_group_edit.mako')
60 def edit_repo_group_delete(self):
60 def edit_repo_group_delete(self):
61 _ = self.request.translate
61 _ = self.request.translate
62 _ungettext = self.request.plularize
62 _ungettext = self.request.plularize
63 c = self.load_default_context()
63 c = self.load_default_context()
64 c.repo_group = self.db_repo_group
64 c.repo_group = self.db_repo_group
65
65
66 repos = c.repo_group.repositories.all()
66 repos = c.repo_group.repositories.all()
67 if repos:
67 if repos:
68 msg = _ungettext(
68 msg = _ungettext(
69 'This repository group contains %(num)d repository and cannot be deleted',
69 'This repository group contains %(num)d repository and cannot be deleted',
70 'This repository group contains %(num)d repositories and cannot be'
70 'This repository group contains %(num)d repositories and cannot be'
71 ' deleted',
71 ' deleted',
72 len(repos)) % {'num': len(repos)}
72 len(repos)) % {'num': len(repos)}
73 h.flash(msg, category='warning')
73 h.flash(msg, category='warning')
74 raise HTTPFound(
74 raise HTTPFound(
75 h.route_path('edit_repo_group_advanced',
75 h.route_path('edit_repo_group_advanced',
76 repo_group_name=self.db_repo_group_name))
76 repo_group_name=self.db_repo_group_name))
77
77
78 children = c.repo_group.children.all()
78 children = c.repo_group.children.all()
79 if children:
79 if children:
80 msg = _ungettext(
80 msg = _ungettext(
81 'This repository group contains %(num)d subgroup and cannot be deleted',
81 'This repository group contains %(num)d subgroup and cannot be deleted',
82 'This repository group contains %(num)d subgroups and cannot be deleted',
82 'This repository group contains %(num)d subgroups and cannot be deleted',
83 len(children)) % {'num': len(children)}
83 len(children)) % {'num': len(children)}
84 h.flash(msg, category='warning')
84 h.flash(msg, category='warning')
85 raise HTTPFound(
85 raise HTTPFound(
86 h.route_path('edit_repo_group_advanced',
86 h.route_path('edit_repo_group_advanced',
87 repo_group_name=self.db_repo_group_name))
87 repo_group_name=self.db_repo_group_name))
88
88
89 try:
89 try:
90 old_values = c.repo_group.get_api_data()
90 old_values = c.repo_group.get_api_data()
91 RepoGroupModel().delete(self.db_repo_group_name)
91 RepoGroupModel().delete(self.db_repo_group_name)
92
92
93 audit_logger.store_web(
93 audit_logger.store_web(
94 'repo_group.delete', action_data={'old_data': old_values},
94 'repo_group.delete', action_data={'old_data': old_values},
95 user=c.rhodecode_user)
95 user=c.rhodecode_user)
96
96
97 Session().commit()
97 Session().commit()
98 h.flash(_('Removed repository group `%s`') % self.db_repo_group_name,
98 h.flash(_('Removed repository group `%s`') % self.db_repo_group_name,
99 category='success')
99 category='success')
100 except Exception:
100 except Exception:
101 log.exception("Exception during deletion of repository group")
101 log.exception("Exception during deletion of repository group")
102 h.flash(_('Error occurred during deletion of repository group %s')
102 h.flash(_('Error occurred during deletion of repository group %s')
103 % self.db_repo_group_name, category='error')
103 % self.db_repo_group_name, category='error')
104
104
105 raise HTTPFound(h.route_path('repo_groups'))
105 raise HTTPFound(h.route_path('repo_groups'))
@@ -1,100 +1,100 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22
22
23 from pyramid.view import view_config
23 from pyramid.view import view_config
24 from pyramid.httpexceptions import HTTPFound
24 from pyramid.httpexceptions import HTTPFound
25
25
26 from rhodecode.apps._base import RepoGroupAppView
26 from rhodecode.apps._base import RepoGroupAppView
27 from rhodecode.lib import helpers as h
27 from rhodecode.lib import helpers as h
28 from rhodecode.lib import audit_logger
28 from rhodecode.lib import audit_logger
29 from rhodecode.lib.auth import (
29 from rhodecode.lib.auth import (
30 LoginRequired, HasRepoGroupPermissionAnyDecorator, CSRFRequired)
30 LoginRequired, HasRepoGroupPermissionAnyDecorator, CSRFRequired)
31 from rhodecode.model.repo_group import RepoGroupModel
31 from rhodecode.model.repo_group import RepoGroupModel
32 from rhodecode.model.forms import RepoGroupPermsForm
32 from rhodecode.model.forms import RepoGroupPermsForm
33 from rhodecode.model.meta import Session
33 from rhodecode.model.meta import Session
34
34
35 log = logging.getLogger(__name__)
35 log = logging.getLogger(__name__)
36
36
37
37
38 class RepoGroupPermissionsView(RepoGroupAppView):
38 class RepoGroupPermissionsView(RepoGroupAppView):
39 def load_default_context(self):
39 def load_default_context(self):
40 c = self._get_local_tmpl_context()
40 c = self._get_local_tmpl_context()
41 self._register_global_c(c)
41
42 return c
42 return c
43
43
44 @LoginRequired()
44 @LoginRequired()
45 @HasRepoGroupPermissionAnyDecorator('group.admin')
45 @HasRepoGroupPermissionAnyDecorator('group.admin')
46 @view_config(
46 @view_config(
47 route_name='edit_repo_group_perms', request_method='GET',
47 route_name='edit_repo_group_perms', request_method='GET',
48 renderer='rhodecode:templates/admin/repo_groups/repo_group_edit.mako')
48 renderer='rhodecode:templates/admin/repo_groups/repo_group_edit.mako')
49 def edit_repo_group_permissions(self):
49 def edit_repo_group_permissions(self):
50 c = self.load_default_context()
50 c = self.load_default_context()
51 c.active = 'permissions'
51 c.active = 'permissions'
52 c.repo_group = self.db_repo_group
52 c.repo_group = self.db_repo_group
53 return self._get_template_context(c)
53 return self._get_template_context(c)
54
54
55 @LoginRequired()
55 @LoginRequired()
56 @HasRepoGroupPermissionAnyDecorator('group.admin')
56 @HasRepoGroupPermissionAnyDecorator('group.admin')
57 @CSRFRequired()
57 @CSRFRequired()
58 @view_config(
58 @view_config(
59 route_name='edit_repo_group_perms_update', request_method='POST',
59 route_name='edit_repo_group_perms_update', request_method='POST',
60 renderer='rhodecode:templates/admin/repo_groups/repo_group_edit.mako')
60 renderer='rhodecode:templates/admin/repo_groups/repo_group_edit.mako')
61 def edit_repo_groups_permissions_update(self):
61 def edit_repo_groups_permissions_update(self):
62 _ = self.request.translate
62 _ = self.request.translate
63 c = self.load_default_context()
63 c = self.load_default_context()
64 c.active = 'perms'
64 c.active = 'perms'
65 c.repo_group = self.db_repo_group
65 c.repo_group = self.db_repo_group
66
66
67 valid_recursive_choices = ['none', 'repos', 'groups', 'all']
67 valid_recursive_choices = ['none', 'repos', 'groups', 'all']
68 form = RepoGroupPermsForm(valid_recursive_choices)()\
68 form = RepoGroupPermsForm(self.request.translate, valid_recursive_choices)()\
69 .to_python(self.request.POST)
69 .to_python(self.request.POST)
70
70
71 if not c.rhodecode_user.is_admin:
71 if not c.rhodecode_user.is_admin:
72 if self._revoke_perms_on_yourself(form):
72 if self._revoke_perms_on_yourself(form):
73 msg = _('Cannot change permission for yourself as admin')
73 msg = _('Cannot change permission for yourself as admin')
74 h.flash(msg, category='warning')
74 h.flash(msg, category='warning')
75 raise HTTPFound(
75 raise HTTPFound(
76 h.route_path('edit_repo_group_perms',
76 h.route_path('edit_repo_group_perms',
77 group_name=self.db_repo_group_name))
77 group_name=self.db_repo_group_name))
78
78
79 # iterate over all members(if in recursive mode) of this groups and
79 # iterate over all members(if in recursive mode) of this groups and
80 # set the permissions !
80 # set the permissions !
81 # this can be potentially heavy operation
81 # this can be potentially heavy operation
82 changes = RepoGroupModel().update_permissions(
82 changes = RepoGroupModel().update_permissions(
83 c.repo_group,
83 c.repo_group,
84 form['perm_additions'], form['perm_updates'], form['perm_deletions'],
84 form['perm_additions'], form['perm_updates'], form['perm_deletions'],
85 form['recursive'])
85 form['recursive'])
86
86
87 action_data = {
87 action_data = {
88 'added': changes['added'],
88 'added': changes['added'],
89 'updated': changes['updated'],
89 'updated': changes['updated'],
90 'deleted': changes['deleted'],
90 'deleted': changes['deleted'],
91 }
91 }
92 audit_logger.store_web(
92 audit_logger.store_web(
93 'repo_group.edit.permissions', action_data=action_data,
93 'repo_group.edit.permissions', action_data=action_data,
94 user=c.rhodecode_user)
94 user=c.rhodecode_user)
95
95
96 Session().commit()
96 Session().commit()
97 h.flash(_('Repository Group permissions updated'), category='success')
97 h.flash(_('Repository Group permissions updated'), category='success')
98 raise HTTPFound(
98 raise HTTPFound(
99 h.route_path('edit_repo_group_perms',
99 h.route_path('edit_repo_group_perms',
100 repo_group_name=self.db_repo_group_name))
100 repo_group_name=self.db_repo_group_name))
@@ -1,183 +1,183 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 import deform
22 import deform
23
23
24 from pyramid.view import view_config
24 from pyramid.view import view_config
25 from pyramid.httpexceptions import HTTPFound
25 from pyramid.httpexceptions import HTTPFound
26
26
27 from rhodecode.apps._base import RepoGroupAppView
27 from rhodecode.apps._base import RepoGroupAppView
28 from rhodecode.forms import RcForm
28 from rhodecode.forms import RcForm
29 from rhodecode.lib import helpers as h
29 from rhodecode.lib import helpers as h
30 from rhodecode.lib import audit_logger
30 from rhodecode.lib import audit_logger
31 from rhodecode.lib.auth import (
31 from rhodecode.lib.auth import (
32 LoginRequired, HasPermissionAll,
32 LoginRequired, HasPermissionAll,
33 HasRepoGroupPermissionAny, HasRepoGroupPermissionAnyDecorator, CSRFRequired)
33 HasRepoGroupPermissionAny, HasRepoGroupPermissionAnyDecorator, CSRFRequired)
34 from rhodecode.model.db import Session, RepoGroup
34 from rhodecode.model.db import Session, RepoGroup
35 from rhodecode.model.scm import RepoGroupList
35 from rhodecode.model.scm import RepoGroupList
36 from rhodecode.model.repo_group import RepoGroupModel
36 from rhodecode.model.repo_group import RepoGroupModel
37 from rhodecode.model.validation_schema.schemas import repo_group_schema
37 from rhodecode.model.validation_schema.schemas import repo_group_schema
38
38
39 log = logging.getLogger(__name__)
39 log = logging.getLogger(__name__)
40
40
41
41
42 class RepoGroupSettingsView(RepoGroupAppView):
42 class RepoGroupSettingsView(RepoGroupAppView):
43 def load_default_context(self):
43 def load_default_context(self):
44 c = self._get_local_tmpl_context()
44 c = self._get_local_tmpl_context()
45 c.repo_group = self.db_repo_group
45 c.repo_group = self.db_repo_group
46 no_parrent = not c.repo_group.parent_group
46 no_parrent = not c.repo_group.parent_group
47 can_create_in_root = self._can_create_repo_group()
47 can_create_in_root = self._can_create_repo_group()
48
48
49 show_root_location = False
49 show_root_location = False
50 if no_parrent or can_create_in_root:
50 if no_parrent or can_create_in_root:
51 # we're global admin, we're ok and we can create TOP level groups
51 # we're global admin, we're ok and we can create TOP level groups
52 # or in case this group is already at top-level we also allow
52 # or in case this group is already at top-level we also allow
53 # creation in root
53 # creation in root
54 show_root_location = True
54 show_root_location = True
55
55
56 acl_groups = RepoGroupList(
56 acl_groups = RepoGroupList(
57 RepoGroup.query().all(),
57 RepoGroup.query().all(),
58 perm_set=['group.admin'])
58 perm_set=['group.admin'])
59 c.repo_groups = RepoGroup.groups_choices(
59 c.repo_groups = RepoGroup.groups_choices(
60 groups=acl_groups,
60 groups=acl_groups,
61 show_empty_group=show_root_location)
61 show_empty_group=show_root_location)
62 # filter out current repo group
62 # filter out current repo group
63 exclude_group_ids = [c.repo_group.group_id]
63 exclude_group_ids = [c.repo_group.group_id]
64 c.repo_groups = filter(lambda x: x[0] not in exclude_group_ids,
64 c.repo_groups = filter(lambda x: x[0] not in exclude_group_ids,
65 c.repo_groups)
65 c.repo_groups)
66 c.repo_groups_choices = map(lambda k: k[0], c.repo_groups)
66 c.repo_groups_choices = map(lambda k: k[0], c.repo_groups)
67
67
68 parent_group = c.repo_group.parent_group
68 parent_group = c.repo_group.parent_group
69
69
70 add_parent_group = (parent_group and (
70 add_parent_group = (parent_group and (
71 parent_group.group_id not in c.repo_groups_choices))
71 parent_group.group_id not in c.repo_groups_choices))
72 if add_parent_group:
72 if add_parent_group:
73 c.repo_groups_choices.append(parent_group.group_id)
73 c.repo_groups_choices.append(parent_group.group_id)
74 c.repo_groups.append(RepoGroup._generate_choice(parent_group))
74 c.repo_groups.append(RepoGroup._generate_choice(parent_group))
75
75
76 self._register_global_c(c)
76
77 return c
77 return c
78
78
79 def _can_create_repo_group(self, parent_group_id=None):
79 def _can_create_repo_group(self, parent_group_id=None):
80 is_admin = HasPermissionAll('hg.admin')('group create controller')
80 is_admin = HasPermissionAll('hg.admin')('group create controller')
81 create_repo_group = HasPermissionAll(
81 create_repo_group = HasPermissionAll(
82 'hg.repogroup.create.true')('group create controller')
82 'hg.repogroup.create.true')('group create controller')
83 if is_admin or (create_repo_group and not parent_group_id):
83 if is_admin or (create_repo_group and not parent_group_id):
84 # we're global admin, or we have global repo group create
84 # we're global admin, or we have global repo group create
85 # permission
85 # permission
86 # we're ok and we can create TOP level groups
86 # we're ok and we can create TOP level groups
87 return True
87 return True
88 elif parent_group_id:
88 elif parent_group_id:
89 # we check the permission if we can write to parent group
89 # we check the permission if we can write to parent group
90 group = RepoGroup.get(parent_group_id)
90 group = RepoGroup.get(parent_group_id)
91 group_name = group.group_name if group else None
91 group_name = group.group_name if group else None
92 if HasRepoGroupPermissionAny('group.admin')(
92 if HasRepoGroupPermissionAny('group.admin')(
93 group_name, 'check if user is an admin of group'):
93 group_name, 'check if user is an admin of group'):
94 # we're an admin of passed in group, we're ok.
94 # we're an admin of passed in group, we're ok.
95 return True
95 return True
96 else:
96 else:
97 return False
97 return False
98 return False
98 return False
99
99
100 def _get_schema(self, c, old_values=None):
100 def _get_schema(self, c, old_values=None):
101 return repo_group_schema.RepoGroupSettingsSchema().bind(
101 return repo_group_schema.RepoGroupSettingsSchema().bind(
102 repo_group_repo_group_options=c.repo_groups_choices,
102 repo_group_repo_group_options=c.repo_groups_choices,
103 repo_group_repo_group_items=c.repo_groups,
103 repo_group_repo_group_items=c.repo_groups,
104
104
105 # user caller
105 # user caller
106 user=self._rhodecode_user,
106 user=self._rhodecode_user,
107 old_values=old_values
107 old_values=old_values
108 )
108 )
109
109
110 @LoginRequired()
110 @LoginRequired()
111 @HasRepoGroupPermissionAnyDecorator('group.admin')
111 @HasRepoGroupPermissionAnyDecorator('group.admin')
112 @view_config(
112 @view_config(
113 route_name='edit_repo_group', request_method='GET',
113 route_name='edit_repo_group', request_method='GET',
114 renderer='rhodecode:templates/admin/repo_groups/repo_group_edit.mako')
114 renderer='rhodecode:templates/admin/repo_groups/repo_group_edit.mako')
115 def edit_settings(self):
115 def edit_settings(self):
116 c = self.load_default_context()
116 c = self.load_default_context()
117 c.active = 'settings'
117 c.active = 'settings'
118
118
119 defaults = RepoGroupModel()._get_defaults(self.db_repo_group_name)
119 defaults = RepoGroupModel()._get_defaults(self.db_repo_group_name)
120 defaults['repo_group_owner'] = defaults['user']
120 defaults['repo_group_owner'] = defaults['user']
121
121
122 schema = self._get_schema(c)
122 schema = self._get_schema(c)
123 c.form = RcForm(schema, appstruct=defaults)
123 c.form = RcForm(schema, appstruct=defaults)
124 return self._get_template_context(c)
124 return self._get_template_context(c)
125
125
126 @LoginRequired()
126 @LoginRequired()
127 @HasRepoGroupPermissionAnyDecorator('group.admin')
127 @HasRepoGroupPermissionAnyDecorator('group.admin')
128 @CSRFRequired()
128 @CSRFRequired()
129 @view_config(
129 @view_config(
130 route_name='edit_repo_group', request_method='POST',
130 route_name='edit_repo_group', request_method='POST',
131 renderer='rhodecode:templates/admin/repo_groups/repo_group_edit.mako')
131 renderer='rhodecode:templates/admin/repo_groups/repo_group_edit.mako')
132 def edit_settings_update(self):
132 def edit_settings_update(self):
133 _ = self.request.translate
133 _ = self.request.translate
134 c = self.load_default_context()
134 c = self.load_default_context()
135 c.active = 'settings'
135 c.active = 'settings'
136
136
137 old_repo_group_name = self.db_repo_group_name
137 old_repo_group_name = self.db_repo_group_name
138 new_repo_group_name = old_repo_group_name
138 new_repo_group_name = old_repo_group_name
139
139
140 old_values = RepoGroupModel()._get_defaults(self.db_repo_group_name)
140 old_values = RepoGroupModel()._get_defaults(self.db_repo_group_name)
141 schema = self._get_schema(c, old_values=old_values)
141 schema = self._get_schema(c, old_values=old_values)
142
142
143 c.form = RcForm(schema)
143 c.form = RcForm(schema)
144 pstruct = self.request.POST.items()
144 pstruct = self.request.POST.items()
145
145
146 try:
146 try:
147 schema_data = c.form.validate(pstruct)
147 schema_data = c.form.validate(pstruct)
148 except deform.ValidationFailure as err_form:
148 except deform.ValidationFailure as err_form:
149 return self._get_template_context(c)
149 return self._get_template_context(c)
150
150
151 # data is now VALID, proceed with updates
151 # data is now VALID, proceed with updates
152 # save validated data back into the updates dict
152 # save validated data back into the updates dict
153 validated_updates = dict(
153 validated_updates = dict(
154 group_name=schema_data['repo_group']['repo_group_name_without_group'],
154 group_name=schema_data['repo_group']['repo_group_name_without_group'],
155 group_parent_id=schema_data['repo_group']['repo_group_id'],
155 group_parent_id=schema_data['repo_group']['repo_group_id'],
156 user=schema_data['repo_group_owner'],
156 user=schema_data['repo_group_owner'],
157 group_description=schema_data['repo_group_description'],
157 group_description=schema_data['repo_group_description'],
158 enable_locking=schema_data['repo_group_enable_locking'],
158 enable_locking=schema_data['repo_group_enable_locking'],
159 )
159 )
160
160
161 try:
161 try:
162 RepoGroupModel().update(self.db_repo_group, validated_updates)
162 RepoGroupModel().update(self.db_repo_group, validated_updates)
163
163
164 audit_logger.store_web(
164 audit_logger.store_web(
165 'repo_group.edit', action_data={'old_data': old_values},
165 'repo_group.edit', action_data={'old_data': old_values},
166 user=c.rhodecode_user)
166 user=c.rhodecode_user)
167
167
168 Session().commit()
168 Session().commit()
169
169
170 # use the new full name for redirect once we know we updated
170 # use the new full name for redirect once we know we updated
171 # the name on filesystem and in DB
171 # the name on filesystem and in DB
172 new_repo_group_name = schema_data['repo_group']['repo_group_name_with_group']
172 new_repo_group_name = schema_data['repo_group']['repo_group_name_with_group']
173
173
174 h.flash(_('Repository Group `{}` updated successfully').format(
174 h.flash(_('Repository Group `{}` updated successfully').format(
175 old_repo_group_name), category='success')
175 old_repo_group_name), category='success')
176
176
177 except Exception:
177 except Exception:
178 log.exception("Exception during update or repository group")
178 log.exception("Exception during update or repository group")
179 h.flash(_('Error occurred during update of repository group %s')
179 h.flash(_('Error occurred during update of repository group %s')
180 % old_repo_group_name, category='error')
180 % old_repo_group_name, category='error')
181
181
182 raise HTTPFound(
182 raise HTTPFound(
183 h.route_path('edit_repo_group', repo_group_name=new_repo_group_name))
183 h.route_path('edit_repo_group', repo_group_name=new_repo_group_name))
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now