Show More
The requested changes are too big and content was truncated. Show full diff
@@ -1,276 +1,255 b'' | |||
|
1 | 1 | # Overrides for the generated python-packages.nix |
|
2 | 2 | # |
|
3 | 3 | # This function is intended to be used as an extension to the generated file |
|
4 | 4 | # python-packages.nix. The main objective is to add needed dependencies of C |
|
5 | 5 | # libraries and tweak the build instructions where needed. |
|
6 | 6 | |
|
7 | 7 | { pkgs, basePythonPackages }: |
|
8 | 8 | |
|
9 | 9 | let |
|
10 | 10 | sed = "sed -i"; |
|
11 | 11 | localLicenses = { |
|
12 | 12 | repoze = { |
|
13 | 13 | fullName = "Repoze License"; |
|
14 | 14 | url = http://www.repoze.org/LICENSE.txt; |
|
15 | 15 | }; |
|
16 | 16 | }; |
|
17 | 17 | |
|
18 | 18 | in |
|
19 | 19 | |
|
20 | 20 | self: super: { |
|
21 | 21 | |
|
22 | 22 | appenlight-client = super.appenlight-client.override (attrs: { |
|
23 | 23 | meta = { |
|
24 | 24 | license = [ pkgs.lib.licenses.bsdOriginal ]; |
|
25 | 25 | }; |
|
26 | 26 | }); |
|
27 | 27 | |
|
28 | 28 | future = super.future.override (attrs: { |
|
29 | 29 | meta = { |
|
30 | 30 | license = [ pkgs.lib.licenses.mit ]; |
|
31 | 31 | }; |
|
32 | 32 | }); |
|
33 | 33 | |
|
34 | 34 | gnureadline = super.gnureadline.override (attrs: { |
|
35 | 35 | buildInputs = attrs.buildInputs ++ [ |
|
36 | 36 | pkgs.ncurses |
|
37 | 37 | ]; |
|
38 | 38 | patchPhase = '' |
|
39 | 39 | substituteInPlace setup.py --replace "/bin/bash" "${pkgs.bash}/bin/bash" |
|
40 | 40 | ''; |
|
41 | 41 | }); |
|
42 | 42 | |
|
43 | 43 | gunicorn = super.gunicorn.override (attrs: { |
|
44 | 44 | propagatedBuildInputs = attrs.propagatedBuildInputs ++ [ |
|
45 | 45 | # johbo: futures is needed as long as we are on Python 2, otherwise |
|
46 | 46 | # gunicorn explodes if used with multiple threads per worker. |
|
47 | 47 | self.futures |
|
48 | 48 | ]; |
|
49 | 49 | }); |
|
50 | 50 | |
|
51 | 51 | nbconvert = super.nbconvert.override (attrs: { |
|
52 | 52 | propagatedBuildInputs = attrs.propagatedBuildInputs ++ [ |
|
53 | 53 | # marcink: plug in jupyter-client for notebook rendering |
|
54 | 54 | self.jupyter-client |
|
55 | 55 | ]; |
|
56 | 56 | }); |
|
57 | 57 | |
|
58 | 58 | ipython = super.ipython.override (attrs: { |
|
59 | 59 | propagatedBuildInputs = attrs.propagatedBuildInputs ++ [ |
|
60 | 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 | 64 | lxml = super.lxml.override (attrs: { |
|
82 | 65 | # johbo: On 16.09 we need this to compile on darwin, otherwise compilation |
|
83 | 66 | # fails on Darwin. |
|
84 | 67 | hardeningDisable = if pkgs.stdenv.isDarwin then [ "format" ] else null; |
|
85 | 68 | buildInputs = with self; [ |
|
86 | 69 | pkgs.libxml2 |
|
87 | 70 | pkgs.libxslt |
|
88 | 71 | ]; |
|
89 | 72 | }); |
|
90 | 73 | |
|
91 | 74 | MySQL-python = super.MySQL-python.override (attrs: { |
|
92 | 75 | buildInputs = attrs.buildInputs ++ [ |
|
93 | 76 | pkgs.openssl |
|
94 | 77 | ]; |
|
95 | 78 | propagatedBuildInputs = attrs.propagatedBuildInputs ++ [ |
|
96 | 79 | pkgs.mysql.lib |
|
97 | 80 | pkgs.zlib |
|
98 | 81 | ]; |
|
99 | 82 | }); |
|
100 | 83 | |
|
101 | 84 | psutil = super.psutil.override (attrs: { |
|
102 | 85 | buildInputs = attrs.buildInputs ++ |
|
103 | 86 | pkgs.lib.optional pkgs.stdenv.isDarwin pkgs.darwin.IOKit; |
|
104 | 87 | }); |
|
105 | 88 | |
|
106 | 89 | psycopg2 = super.psycopg2.override (attrs: { |
|
107 | 90 | buildInputs = attrs.buildInputs ++ |
|
108 | 91 | pkgs.lib.optional pkgs.stdenv.isDarwin pkgs.openssl; |
|
109 | 92 | propagatedBuildInputs = attrs.propagatedBuildInputs ++ [ |
|
110 | 93 | pkgs.postgresql |
|
111 | 94 | ]; |
|
112 | 95 | meta = { |
|
113 | 96 | license = pkgs.lib.licenses.lgpl3Plus; |
|
114 | 97 | }; |
|
115 | 98 | }); |
|
116 | 99 | |
|
117 | 100 | py-gfm = super.py-gfm.override { |
|
118 | 101 | name = "py-gfm-0.1.3.rhodecode-upstream1"; |
|
119 | 102 | }; |
|
120 | 103 | |
|
121 | 104 | pycurl = super.pycurl.override (attrs: { |
|
122 | 105 | propagatedBuildInputs = attrs.propagatedBuildInputs ++ [ |
|
123 | 106 | pkgs.curl |
|
124 | 107 | pkgs.openssl |
|
125 | 108 | ]; |
|
126 | 109 | preConfigure = '' |
|
127 | 110 | substituteInPlace setup.py --replace '--static-libs' '--libs' |
|
128 | 111 | export PYCURL_SSL_LIBRARY=openssl |
|
129 | 112 | ''; |
|
130 | 113 | meta = { |
|
131 | 114 | # TODO: It is LGPL and MIT |
|
132 | 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 | 119 | pyramid = super.pyramid.override (attrs: { |
|
141 | 120 | postFixup = '' |
|
142 | 121 | wrapPythonPrograms |
|
143 | 122 | # TODO: johbo: "wrapPython" adds this magic line which |
|
144 | 123 | # confuses pserve. |
|
145 | 124 | ${sed} '/import sys; sys.argv/d' $out/bin/.pserve-wrapped |
|
146 | 125 | ''; |
|
147 | 126 | meta = { |
|
148 | 127 | license = localLicenses.repoze; |
|
149 | 128 | }; |
|
150 | 129 | }); |
|
151 | 130 | |
|
152 | 131 | pyramid-debugtoolbar = super.pyramid-debugtoolbar.override (attrs: { |
|
153 | 132 | meta = { |
|
154 | 133 | license = [ pkgs.lib.licenses.bsdOriginal localLicenses.repoze ]; |
|
155 | 134 | }; |
|
156 | 135 | }); |
|
157 | 136 | |
|
158 | 137 | pysqlite = super.pysqlite.override (attrs: { |
|
159 | 138 | propagatedBuildInputs = [ |
|
160 | 139 | pkgs.sqlite |
|
161 | 140 | ]; |
|
162 | 141 | meta = { |
|
163 | 142 | license = [ pkgs.lib.licenses.zlib pkgs.lib.licenses.libpng ]; |
|
164 | 143 | }; |
|
165 | 144 | }); |
|
166 | 145 | |
|
167 | 146 | pytest-runner = super.pytest-runner.override (attrs: { |
|
168 | 147 | propagatedBuildInputs = [ |
|
169 | 148 | self.setuptools-scm |
|
170 | 149 | ]; |
|
171 | 150 | }); |
|
172 | 151 | |
|
173 | 152 | python-ldap = super.python-ldap.override (attrs: { |
|
174 | 153 | propagatedBuildInputs = attrs.propagatedBuildInputs ++ [ |
|
175 | 154 | pkgs.cyrus_sasl |
|
176 | 155 | pkgs.openldap |
|
177 | 156 | pkgs.openssl |
|
178 | 157 | ]; |
|
179 | 158 | # TODO: johbo: Remove the "or" once we drop 16.03 support. |
|
180 | 159 | NIX_CFLAGS_COMPILE = "-I${pkgs.cyrus_sasl.dev or pkgs.cyrus_sasl}/include/sasl"; |
|
181 | 160 | }); |
|
182 | 161 | |
|
183 | 162 | python-pam = super.python-pam.override (attrs: |
|
184 | 163 | let |
|
185 | 164 | includeLibPam = pkgs.stdenv.isLinux; |
|
186 | 165 | in { |
|
187 | 166 | # TODO: johbo: Move the option up into the default.nix, we should |
|
188 | 167 | # include python-pam only on supported platforms. |
|
189 | 168 | propagatedBuildInputs = attrs.propagatedBuildInputs ++ |
|
190 | 169 | pkgs.lib.optional includeLibPam [ |
|
191 | 170 | pkgs.pam |
|
192 | 171 | ]; |
|
193 | 172 | # TODO: johbo: Check if this can be avoided, or transform into |
|
194 | 173 | # a real patch |
|
195 | 174 | patchPhase = pkgs.lib.optionals includeLibPam '' |
|
196 | 175 | substituteInPlace pam.py \ |
|
197 | 176 | --replace 'find_library("pam")' '"${pkgs.pam}/lib/libpam.so.0"' |
|
198 | 177 | ''; |
|
199 | 178 | }); |
|
200 | 179 | |
|
201 | 180 | URLObject = super.URLObject.override (attrs: { |
|
202 | 181 | meta = { |
|
203 | 182 | license = { |
|
204 | 183 | spdxId = "Unlicense"; |
|
205 | 184 | fullName = "The Unlicense"; |
|
206 | 185 | url = http://unlicense.org/; |
|
207 | 186 | }; |
|
208 | 187 | }; |
|
209 | 188 | }); |
|
210 | 189 | |
|
211 | 190 | amqplib = super.amqplib.override (attrs: { |
|
212 | 191 | meta = { |
|
213 | 192 | license = pkgs.lib.licenses.lgpl3; |
|
214 | 193 | }; |
|
215 | 194 | }); |
|
216 | 195 | |
|
217 | 196 | docutils = super.docutils.override (attrs: { |
|
218 | 197 | meta = { |
|
219 | 198 | license = pkgs.lib.licenses.bsd2; |
|
220 | 199 | }; |
|
221 | 200 | }); |
|
222 | 201 | |
|
223 | 202 | colander = super.colander.override (attrs: { |
|
224 | 203 | meta = { |
|
225 | 204 | license = localLicenses.repoze; |
|
226 | 205 | }; |
|
227 | 206 | }); |
|
228 | 207 | |
|
229 | 208 | pyramid-beaker = super.pyramid-beaker.override (attrs: { |
|
230 | 209 | meta = { |
|
231 | 210 | license = localLicenses.repoze; |
|
232 | 211 | }; |
|
233 | 212 | }); |
|
234 | 213 | |
|
235 | 214 | pyramid-mako = super.pyramid-mako.override (attrs: { |
|
236 | 215 | meta = { |
|
237 | 216 | license = localLicenses.repoze; |
|
238 | 217 | }; |
|
239 | 218 | }); |
|
240 | 219 | |
|
241 | 220 | repoze.lru = super.repoze.lru.override (attrs: { |
|
242 | 221 | meta = { |
|
243 | 222 | license = localLicenses.repoze; |
|
244 | 223 | }; |
|
245 | 224 | }); |
|
246 | 225 | |
|
247 | 226 | recaptcha-client = super.recaptcha-client.override (attrs: { |
|
248 | 227 | meta = { |
|
249 | 228 | # TODO: It is MIT/X11 |
|
250 | 229 | license = pkgs.lib.licenses.mit; |
|
251 | 230 | }; |
|
252 | 231 | }); |
|
253 | 232 | |
|
254 | 233 | python-editor = super.python-editor.override (attrs: { |
|
255 | 234 | meta = { |
|
256 | 235 | license = pkgs.lib.licenses.asl20; |
|
257 | 236 | }; |
|
258 | 237 | }); |
|
259 | 238 | |
|
260 | 239 | translationstring = super.translationstring.override (attrs: { |
|
261 | 240 | meta = { |
|
262 | 241 | license = localLicenses.repoze; |
|
263 | 242 | }; |
|
264 | 243 | }); |
|
265 | 244 | |
|
266 | 245 | venusian = super.venusian.override (attrs: { |
|
267 | 246 | meta = { |
|
268 | 247 | license = localLicenses.repoze; |
|
269 | 248 | }; |
|
270 | 249 | }); |
|
271 | 250 | |
|
272 | 251 | # Avoid that setuptools is replaced, this leads to trouble |
|
273 | 252 | # with buildPythonPackage. |
|
274 | 253 | setuptools = basePythonPackages.setuptools; |
|
275 | 254 | |
|
276 | 255 | } |
@@ -1,2060 +1,2060 b'' | |||
|
1 | 1 | # Generated by pip2nix 0.4.0 |
|
2 | 2 | # See https://github.com/johbo/pip2nix |
|
3 | 3 | |
|
4 | 4 | { |
|
5 | 5 | Babel = super.buildPythonPackage { |
|
6 | 6 | name = "Babel-1.3"; |
|
7 | 7 | buildInputs = with self; []; |
|
8 | 8 | doCheck = false; |
|
9 | 9 | propagatedBuildInputs = with self; [pytz]; |
|
10 | 10 | src = fetchurl { |
|
11 | 11 | url = "https://pypi.python.org/packages/33/27/e3978243a03a76398c384c83f7ca879bc6e8f1511233a621fcada135606e/Babel-1.3.tar.gz"; |
|
12 | 12 | md5 = "5264ceb02717843cbc9ffce8e6e06bdb"; |
|
13 | 13 | }; |
|
14 | 14 | meta = { |
|
15 | 15 | license = [ pkgs.lib.licenses.bsdOriginal ]; |
|
16 | 16 | }; |
|
17 | 17 | }; |
|
18 | 18 | Beaker = super.buildPythonPackage { |
|
19 | 19 | name = "Beaker-1.9.0"; |
|
20 | 20 | buildInputs = with self; []; |
|
21 | 21 | doCheck = false; |
|
22 | 22 | propagatedBuildInputs = with self; [funcsigs]; |
|
23 | 23 | src = fetchurl { |
|
24 | 24 | url = "https://pypi.python.org/packages/93/b2/12de6937b06e9615dbb3cb3a1c9af17f133f435bdef59f4ad42032b6eb49/Beaker-1.9.0.tar.gz"; |
|
25 | 25 | md5 = "38b3fcdfa24faf97c6cf66991eb54e9c"; |
|
26 | 26 | }; |
|
27 | 27 | meta = { |
|
28 | 28 | license = [ pkgs.lib.licenses.bsdOriginal ]; |
|
29 | 29 | }; |
|
30 | 30 | }; |
|
31 | 31 | CProfileV = super.buildPythonPackage { |
|
32 | 32 | name = "CProfileV-1.0.7"; |
|
33 | 33 | buildInputs = with self; []; |
|
34 | 34 | doCheck = false; |
|
35 | 35 | propagatedBuildInputs = with self; [bottle]; |
|
36 | 36 | src = fetchurl { |
|
37 | 37 | url = "https://pypi.python.org/packages/df/50/d8c1ada7d537c64b0f76453fa31dedb6af6e27b82fcf0331e5f71a4cf98b/CProfileV-1.0.7.tar.gz"; |
|
38 | 38 | md5 = "db4c7640438aa3d8887e194c81c7a019"; |
|
39 | 39 | }; |
|
40 | 40 | meta = { |
|
41 | 41 | license = [ pkgs.lib.licenses.mit ]; |
|
42 | 42 | }; |
|
43 | 43 | }; |
|
44 | 44 | Chameleon = super.buildPythonPackage { |
|
45 | 45 | name = "Chameleon-2.24"; |
|
46 | 46 | buildInputs = with self; []; |
|
47 | 47 | doCheck = false; |
|
48 | 48 | propagatedBuildInputs = with self; []; |
|
49 | 49 | src = fetchurl { |
|
50 | 50 | url = "https://pypi.python.org/packages/5a/9e/637379ffa13c5172b5c0e704833ffea6bf51cec7567f93fd6e903d53ed74/Chameleon-2.24.tar.gz"; |
|
51 | 51 | md5 = "1b01f1f6533a8a11d0d2f2366dec5342"; |
|
52 | 52 | }; |
|
53 | 53 | meta = { |
|
54 | 54 | license = [ { fullName = "BSD-like (http://repoze.org/license.html)"; } ]; |
|
55 | 55 | }; |
|
56 | 56 | }; |
|
57 | 57 | FormEncode = super.buildPythonPackage { |
|
58 | 58 | name = "FormEncode-1.2.4"; |
|
59 | 59 | buildInputs = with self; []; |
|
60 | 60 | doCheck = false; |
|
61 | 61 | propagatedBuildInputs = with self; []; |
|
62 | 62 | src = fetchurl { |
|
63 | 63 | url = "https://pypi.python.org/packages/8e/59/0174271a6f004512e0201188593e6d319db139d14cb7490e488bbb078015/FormEncode-1.2.4.tar.gz"; |
|
64 | 64 | md5 = "6bc17fb9aed8aea198975e888e2077f4"; |
|
65 | 65 | }; |
|
66 | 66 | meta = { |
|
67 | 67 | license = [ pkgs.lib.licenses.psfl ]; |
|
68 | 68 | }; |
|
69 | 69 | }; |
|
70 | 70 | Jinja2 = super.buildPythonPackage { |
|
71 | 71 | name = "Jinja2-2.9.6"; |
|
72 | 72 | buildInputs = with self; []; |
|
73 | 73 | doCheck = false; |
|
74 | 74 | propagatedBuildInputs = with self; [MarkupSafe]; |
|
75 | 75 | src = fetchurl { |
|
76 | 76 | url = "https://pypi.python.org/packages/90/61/f820ff0076a2599dd39406dcb858ecb239438c02ce706c8e91131ab9c7f1/Jinja2-2.9.6.tar.gz"; |
|
77 | 77 | md5 = "6411537324b4dba0956aaa8109f3c77b"; |
|
78 | 78 | }; |
|
79 | 79 | meta = { |
|
80 | 80 | license = [ pkgs.lib.licenses.bsdOriginal ]; |
|
81 | 81 | }; |
|
82 | 82 | }; |
|
83 | 83 | Mako = super.buildPythonPackage { |
|
84 | 84 | name = "Mako-1.0.7"; |
|
85 | 85 | buildInputs = with self; []; |
|
86 | 86 | doCheck = false; |
|
87 | 87 | propagatedBuildInputs = with self; [MarkupSafe]; |
|
88 | 88 | src = fetchurl { |
|
89 | 89 | url = "https://pypi.python.org/packages/eb/f3/67579bb486517c0d49547f9697e36582cd19dafb5df9e687ed8e22de57fa/Mako-1.0.7.tar.gz"; |
|
90 | 90 | md5 = "5836cc997b1b773ef389bf6629c30e65"; |
|
91 | 91 | }; |
|
92 | 92 | meta = { |
|
93 | 93 | license = [ pkgs.lib.licenses.mit ]; |
|
94 | 94 | }; |
|
95 | 95 | }; |
|
96 | 96 | Markdown = super.buildPythonPackage { |
|
97 | 97 | name = "Markdown-2.6.9"; |
|
98 | 98 | buildInputs = with self; []; |
|
99 | 99 | doCheck = false; |
|
100 | 100 | propagatedBuildInputs = with self; []; |
|
101 | 101 | src = fetchurl { |
|
102 | 102 | url = "https://pypi.python.org/packages/29/82/dfe242bcfd9eec0e7bf93a80a8f8d8515a95b980c44f5c0b45606397a423/Markdown-2.6.9.tar.gz"; |
|
103 | 103 | md5 = "56547d362a9abcf30955b8950b08b5e3"; |
|
104 | 104 | }; |
|
105 | 105 | meta = { |
|
106 | 106 | license = [ pkgs.lib.licenses.bsdOriginal ]; |
|
107 | 107 | }; |
|
108 | 108 | }; |
|
109 | 109 | MarkupSafe = super.buildPythonPackage { |
|
110 | 110 | name = "MarkupSafe-1.0"; |
|
111 | 111 | buildInputs = with self; []; |
|
112 | 112 | doCheck = false; |
|
113 | 113 | propagatedBuildInputs = with self; []; |
|
114 | 114 | src = fetchurl { |
|
115 | 115 | url = "https://pypi.python.org/packages/4d/de/32d741db316d8fdb7680822dd37001ef7a448255de9699ab4bfcbdf4172b/MarkupSafe-1.0.tar.gz"; |
|
116 | 116 | md5 = "2fcedc9284d50e577b5192e8e3578355"; |
|
117 | 117 | }; |
|
118 | 118 | meta = { |
|
119 | 119 | license = [ pkgs.lib.licenses.bsdOriginal ]; |
|
120 | 120 | }; |
|
121 | 121 | }; |
|
122 | 122 | MySQL-python = super.buildPythonPackage { |
|
123 | 123 | name = "MySQL-python-1.2.5"; |
|
124 | 124 | buildInputs = with self; []; |
|
125 | 125 | doCheck = false; |
|
126 | 126 | propagatedBuildInputs = with self; []; |
|
127 | 127 | src = fetchurl { |
|
128 | 128 | url = "https://pypi.python.org/packages/a5/e9/51b544da85a36a68debe7a7091f068d802fc515a3a202652828c73453cad/MySQL-python-1.2.5.zip"; |
|
129 | 129 | md5 = "654f75b302db6ed8dc5a898c625e030c"; |
|
130 | 130 | }; |
|
131 | 131 | meta = { |
|
132 | 132 | license = [ pkgs.lib.licenses.gpl1 ]; |
|
133 | 133 | }; |
|
134 | 134 | }; |
|
135 | 135 | Paste = super.buildPythonPackage { |
|
136 | 136 | name = "Paste-2.0.3"; |
|
137 | 137 | buildInputs = with self; []; |
|
138 | 138 | doCheck = false; |
|
139 | 139 | propagatedBuildInputs = with self; [six]; |
|
140 | 140 | src = fetchurl { |
|
141 | 141 | url = "https://pypi.python.org/packages/30/c3/5c2f7c7a02e4f58d4454353fa1c32c94f79fa4e36d07a67c0ac295ea369e/Paste-2.0.3.tar.gz"; |
|
142 | 142 | md5 = "1231e14eae62fa7ed76e9130b04bc61e"; |
|
143 | 143 | }; |
|
144 | 144 | meta = { |
|
145 | 145 | license = [ pkgs.lib.licenses.mit ]; |
|
146 | 146 | }; |
|
147 | 147 | }; |
|
148 | 148 | PasteDeploy = super.buildPythonPackage { |
|
149 | 149 | name = "PasteDeploy-1.5.2"; |
|
150 | 150 | buildInputs = with self; []; |
|
151 | 151 | doCheck = false; |
|
152 | 152 | propagatedBuildInputs = with self; []; |
|
153 | 153 | src = fetchurl { |
|
154 | 154 | url = "https://pypi.python.org/packages/0f/90/8e20cdae206c543ea10793cbf4136eb9a8b3f417e04e40a29d72d9922cbd/PasteDeploy-1.5.2.tar.gz"; |
|
155 | 155 | md5 = "352b7205c78c8de4987578d19431af3b"; |
|
156 | 156 | }; |
|
157 | 157 | meta = { |
|
158 | 158 | license = [ pkgs.lib.licenses.mit ]; |
|
159 | 159 | }; |
|
160 | 160 | }; |
|
161 | 161 | PasteScript = super.buildPythonPackage { |
|
162 | 162 | name = "PasteScript-2.0.2"; |
|
163 | 163 | buildInputs = with self; []; |
|
164 | 164 | doCheck = false; |
|
165 | 165 | propagatedBuildInputs = with self; [Paste PasteDeploy six]; |
|
166 | 166 | src = fetchurl { |
|
167 | 167 | url = "https://pypi.python.org/packages/e5/f0/78e766c3dcc61a4f3a6f71dd8c95168ae9c7a31722b5663d19c1fdf62cb6/PasteScript-2.0.2.tar.gz"; |
|
168 | 168 | md5 = "ccb3045445097192ca71a13b746c77b2"; |
|
169 | 169 | }; |
|
170 | 170 | meta = { |
|
171 | 171 | license = [ pkgs.lib.licenses.mit ]; |
|
172 | 172 | }; |
|
173 | 173 | }; |
|
174 | 174 | Pygments = super.buildPythonPackage { |
|
175 | 175 | name = "Pygments-2.2.0"; |
|
176 | 176 | buildInputs = with self; []; |
|
177 | 177 | doCheck = false; |
|
178 | 178 | propagatedBuildInputs = with self; []; |
|
179 | 179 | src = fetchurl { |
|
180 | 180 | url = "https://pypi.python.org/packages/71/2a/2e4e77803a8bd6408a2903340ac498cb0a2181811af7c9ec92cb70b0308a/Pygments-2.2.0.tar.gz"; |
|
181 | 181 | md5 = "13037baca42f16917cbd5ad2fab50844"; |
|
182 | 182 | }; |
|
183 | 183 | meta = { |
|
184 | 184 | license = [ pkgs.lib.licenses.bsdOriginal ]; |
|
185 | 185 | }; |
|
186 | 186 | }; |
|
187 |
|
|
|
188 |
name = " |
|
|
187 | Routes = super.buildPythonPackage { | |
|
188 | name = "Routes-2.4.1"; | |
|
189 | 189 | buildInputs = with self; []; |
|
190 | 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 | 192 | src = fetchurl { |
|
193 | url = "https://code.rhodecode.com/upstream/pylons/archive/707354ee4261b9c10450404fc9852ccea4fd667d.tar.gz?md5=f26633726fa2cd3a340316ee6a5d218f"; | |
|
194 | md5 = "f26633726fa2cd3a340316ee6a5d218f"; | |
|
193 | url = "https://pypi.python.org/packages/33/38/ea827837e68d9c7dde4cff7ec122a93c319f0effc08ce92a17095576603f/Routes-2.4.1.tar.gz"; | |
|
194 | md5 = "c058dff6832941dec47e0d0052548ad8"; | |
|
195 | 195 | }; |
|
196 | 196 | meta = { |
|
197 |
license = [ pkgs.lib.licenses. |
|
|
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 ]; | |
|
197 | license = [ pkgs.lib.licenses.mit ]; | |
|
211 | 198 | }; |
|
212 | 199 | }; |
|
213 | 200 | SQLAlchemy = super.buildPythonPackage { |
|
214 | 201 | name = "SQLAlchemy-1.1.15"; |
|
215 | 202 | buildInputs = with self; []; |
|
216 | 203 | doCheck = false; |
|
217 | 204 | propagatedBuildInputs = with self; []; |
|
218 | 205 | src = fetchurl { |
|
219 | 206 | url = "https://pypi.python.org/packages/c2/f6/11fcc1ce19a7cb81b1c9377f4e27ce3813265611922e355905e57c44d164/SQLAlchemy-1.1.15.tar.gz"; |
|
220 | 207 | md5 = "077f9bd3339957f53068b5572a152674"; |
|
221 | 208 | }; |
|
222 | 209 | meta = { |
|
223 | 210 | license = [ pkgs.lib.licenses.mit ]; |
|
224 | 211 | }; |
|
225 | 212 | }; |
|
226 | 213 | Tempita = super.buildPythonPackage { |
|
227 | 214 | name = "Tempita-0.5.2"; |
|
228 | 215 | buildInputs = with self; []; |
|
229 | 216 | doCheck = false; |
|
230 | 217 | propagatedBuildInputs = with self; []; |
|
231 | 218 | src = fetchurl { |
|
232 | 219 | url = "https://pypi.python.org/packages/56/c8/8ed6eee83dbddf7b0fc64dd5d4454bc05e6ccaafff47991f73f2894d9ff4/Tempita-0.5.2.tar.gz"; |
|
233 | 220 | md5 = "4c2f17bb9d481821c41b6fbee904cea1"; |
|
234 | 221 | }; |
|
235 | 222 | meta = { |
|
236 | 223 | license = [ pkgs.lib.licenses.mit ]; |
|
237 | 224 | }; |
|
238 | 225 | }; |
|
239 | 226 | URLObject = super.buildPythonPackage { |
|
240 | 227 | name = "URLObject-2.4.0"; |
|
241 | 228 | buildInputs = with self; []; |
|
242 | 229 | doCheck = false; |
|
243 | 230 | propagatedBuildInputs = with self; []; |
|
244 | 231 | src = fetchurl { |
|
245 | 232 | url = "https://pypi.python.org/packages/cb/b6/e25e58500f9caef85d664bec71ec67c116897bfebf8622c32cb75d1ca199/URLObject-2.4.0.tar.gz"; |
|
246 | 233 | md5 = "2ed819738a9f0a3051f31dc9924e3065"; |
|
247 | 234 | }; |
|
248 | 235 | meta = { |
|
249 | 236 | license = [ ]; |
|
250 | 237 | }; |
|
251 | 238 | }; |
|
252 | 239 | WebError = super.buildPythonPackage { |
|
253 | 240 | name = "WebError-0.10.3"; |
|
254 | 241 | buildInputs = with self; []; |
|
255 | 242 | doCheck = false; |
|
256 | 243 | propagatedBuildInputs = with self; [WebOb Tempita Pygments Paste]; |
|
257 | 244 | src = fetchurl { |
|
258 | 245 | url = "https://pypi.python.org/packages/35/76/e7e5c2ce7e9c7f31b54c1ff295a495886d1279a002557d74dd8957346a79/WebError-0.10.3.tar.gz"; |
|
259 | 246 | md5 = "84b9990b0baae6fd440b1e60cdd06f9a"; |
|
260 | 247 | }; |
|
261 | 248 | meta = { |
|
262 | 249 | license = [ pkgs.lib.licenses.mit ]; |
|
263 | 250 | }; |
|
264 | 251 | }; |
|
265 | 252 | WebHelpers = super.buildPythonPackage { |
|
266 | 253 | name = "WebHelpers-1.3"; |
|
267 | 254 | buildInputs = with self; []; |
|
268 | 255 | doCheck = false; |
|
269 | 256 | propagatedBuildInputs = with self; [MarkupSafe]; |
|
270 | 257 | src = fetchurl { |
|
271 | 258 | url = "https://pypi.python.org/packages/ee/68/4d07672821d514184357f1552f2dad923324f597e722de3b016ca4f7844f/WebHelpers-1.3.tar.gz"; |
|
272 | 259 | md5 = "32749ffadfc40fea51075a7def32588b"; |
|
273 | 260 | }; |
|
274 | 261 | meta = { |
|
275 | 262 | license = [ pkgs.lib.licenses.bsdOriginal ]; |
|
276 | 263 | }; |
|
277 | 264 | }; |
|
278 | 265 | WebHelpers2 = super.buildPythonPackage { |
|
279 | 266 | name = "WebHelpers2-2.0"; |
|
280 | 267 | buildInputs = with self; []; |
|
281 | 268 | doCheck = false; |
|
282 | 269 | propagatedBuildInputs = with self; [MarkupSafe six]; |
|
283 | 270 | src = fetchurl { |
|
284 | 271 | url = "https://pypi.python.org/packages/ff/30/56342c6ea522439e3662427c8d7b5e5b390dff4ff2dc92d8afcb8ab68b75/WebHelpers2-2.0.tar.gz"; |
|
285 | 272 | md5 = "0f6b68d70c12ee0aed48c00b24da13d3"; |
|
286 | 273 | }; |
|
287 | 274 | meta = { |
|
288 | 275 | license = [ pkgs.lib.licenses.mit ]; |
|
289 | 276 | }; |
|
290 | 277 | }; |
|
291 | 278 | WebOb = super.buildPythonPackage { |
|
292 | 279 | name = "WebOb-1.7.3"; |
|
293 | 280 | buildInputs = with self; []; |
|
294 | 281 | doCheck = false; |
|
295 | 282 | propagatedBuildInputs = with self; []; |
|
296 | 283 | src = fetchurl { |
|
297 | 284 | url = "https://pypi.python.org/packages/46/87/2f96d8d43b2078fae6e1d33fa86b95c228cebed060f4e3c7576cc44ea83b/WebOb-1.7.3.tar.gz"; |
|
298 | 285 | md5 = "350028baffc508e3d23c078118e35316"; |
|
299 | 286 | }; |
|
300 | 287 | meta = { |
|
301 | 288 | license = [ pkgs.lib.licenses.mit ]; |
|
302 | 289 | }; |
|
303 | 290 | }; |
|
304 | 291 | WebTest = super.buildPythonPackage { |
|
305 | 292 | name = "WebTest-2.0.29"; |
|
306 | 293 | buildInputs = with self; []; |
|
307 | 294 | doCheck = false; |
|
308 | 295 | propagatedBuildInputs = with self; [six WebOb waitress beautifulsoup4]; |
|
309 | 296 | src = fetchurl { |
|
310 | 297 | url = "https://pypi.python.org/packages/94/de/8f94738be649997da99c47b104aa3c3984ecec51a1d8153ed09638253d56/WebTest-2.0.29.tar.gz"; |
|
311 | 298 | md5 = "30b4cf0d340b9a5335fac4389e6f84fc"; |
|
312 | 299 | }; |
|
313 | 300 | meta = { |
|
314 | 301 | license = [ pkgs.lib.licenses.mit ]; |
|
315 | 302 | }; |
|
316 | 303 | }; |
|
317 | 304 | Whoosh = super.buildPythonPackage { |
|
318 | 305 | name = "Whoosh-2.7.4"; |
|
319 | 306 | buildInputs = with self; []; |
|
320 | 307 | doCheck = false; |
|
321 | 308 | propagatedBuildInputs = with self; []; |
|
322 | 309 | src = fetchurl { |
|
323 | 310 | url = "https://pypi.python.org/packages/25/2b/6beed2107b148edc1321da0d489afc4617b9ed317ef7b72d4993cad9b684/Whoosh-2.7.4.tar.gz"; |
|
324 | 311 | md5 = "c2710105f20b3e29936bd2357383c325"; |
|
325 | 312 | }; |
|
326 | 313 | meta = { |
|
327 | 314 | license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.bsd2 ]; |
|
328 | 315 | }; |
|
329 | 316 | }; |
|
330 | 317 | alembic = super.buildPythonPackage { |
|
331 | 318 | name = "alembic-0.9.6"; |
|
332 | 319 | buildInputs = with self; []; |
|
333 | 320 | doCheck = false; |
|
334 | 321 | propagatedBuildInputs = with self; [SQLAlchemy Mako python-editor python-dateutil]; |
|
335 | 322 | src = fetchurl { |
|
336 | 323 | url = "https://pypi.python.org/packages/bf/b3/b28ea715824f8455635ece3c12f59d5d205f98cc378858e414e3aa6ebdbc/alembic-0.9.6.tar.gz"; |
|
337 | 324 | md5 = "fcb096bccc87c8770bd07a04606cb989"; |
|
338 | 325 | }; |
|
339 | 326 | meta = { |
|
340 | 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 | 343 | amqplib = super.buildPythonPackage { |
|
344 | 344 | name = "amqplib-1.0.2"; |
|
345 | 345 | buildInputs = with self; []; |
|
346 | 346 | doCheck = false; |
|
347 | 347 | propagatedBuildInputs = with self; []; |
|
348 | 348 | src = fetchurl { |
|
349 | 349 | url = "https://pypi.python.org/packages/75/b7/8c2429bf8d92354a0118614f9a4d15e53bc69ebedce534284111de5a0102/amqplib-1.0.2.tgz"; |
|
350 | 350 | md5 = "5c92f17fbedd99b2b4a836d4352d1e2f"; |
|
351 | 351 | }; |
|
352 | 352 | meta = { |
|
353 | 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 | 356 | appenlight-client = super.buildPythonPackage { |
|
370 | 357 | name = "appenlight-client-0.6.22"; |
|
371 | 358 | buildInputs = with self; []; |
|
372 | 359 | doCheck = false; |
|
373 | 360 | propagatedBuildInputs = with self; [WebOb requests six]; |
|
374 | 361 | src = fetchurl { |
|
375 | 362 | url = "https://pypi.python.org/packages/73/37/0a64460fa9670b17c061adc433bc8be5079cba21e8b3a92d824adccb12bc/appenlight_client-0.6.22.tar.gz"; |
|
376 | 363 | md5 = "641afc114a9a3b3af4f75b11c70968ee"; |
|
377 | 364 | }; |
|
378 | 365 | meta = { |
|
379 | 366 | license = [ pkgs.lib.licenses.bsdOriginal ]; |
|
380 | 367 | }; |
|
381 | 368 | }; |
|
382 | 369 | authomatic = super.buildPythonPackage { |
|
383 | 370 | name = "authomatic-0.1.0.post1"; |
|
384 | 371 | buildInputs = with self; []; |
|
385 | 372 | doCheck = false; |
|
386 | 373 | propagatedBuildInputs = with self; []; |
|
387 | 374 | src = fetchurl { |
|
388 | 375 | url = "https://pypi.python.org/packages/08/1a/8a930461e604c2d5a7a871e1ac59fa82ccf994c32e807230c8d2fb07815a/Authomatic-0.1.0.post1.tar.gz"; |
|
389 | 376 | md5 = "be3f3ce08747d776aae6d6cc8dcb49a9"; |
|
390 | 377 | }; |
|
391 | 378 | meta = { |
|
392 | 379 | license = [ pkgs.lib.licenses.mit ]; |
|
393 | 380 | }; |
|
394 | 381 | }; |
|
395 | 382 | backports.shutil-get-terminal-size = super.buildPythonPackage { |
|
396 | 383 | name = "backports.shutil-get-terminal-size-1.0.0"; |
|
397 | 384 | buildInputs = with self; []; |
|
398 | 385 | doCheck = false; |
|
399 | 386 | propagatedBuildInputs = with self; []; |
|
400 | 387 | src = fetchurl { |
|
401 | 388 | url = "https://pypi.python.org/packages/ec/9c/368086faa9c016efce5da3e0e13ba392c9db79e3ab740b763fe28620b18b/backports.shutil_get_terminal_size-1.0.0.tar.gz"; |
|
402 | 389 | md5 = "03267762480bd86b50580dc19dff3c66"; |
|
403 | 390 | }; |
|
404 | 391 | meta = { |
|
405 | 392 | license = [ pkgs.lib.licenses.mit ]; |
|
406 | 393 | }; |
|
407 | 394 | }; |
|
408 | 395 | beautifulsoup4 = super.buildPythonPackage { |
|
409 | 396 | name = "beautifulsoup4-4.6.0"; |
|
410 | 397 | buildInputs = with self; []; |
|
411 | 398 | doCheck = false; |
|
412 | 399 | propagatedBuildInputs = with self; []; |
|
413 | 400 | src = fetchurl { |
|
414 | 401 | url = "https://pypi.python.org/packages/fa/8d/1d14391fdaed5abada4e0f63543fef49b8331a34ca60c88bd521bcf7f782/beautifulsoup4-4.6.0.tar.gz"; |
|
415 | 402 | md5 = "c17714d0f91a23b708a592cb3c697728"; |
|
416 | 403 | }; |
|
417 | 404 | meta = { |
|
418 | 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 | 421 | bleach = super.buildPythonPackage { |
|
422 | 422 | name = "bleach-1.5.0"; |
|
423 | 423 | buildInputs = with self; []; |
|
424 | 424 | doCheck = false; |
|
425 | 425 | propagatedBuildInputs = with self; [six html5lib]; |
|
426 | 426 | src = fetchurl { |
|
427 | 427 | url = "https://pypi.python.org/packages/99/00/25a8fce4de102bf6e3cc76bc4ea60685b2fee33bde1b34830c70cacc26a7/bleach-1.5.0.tar.gz"; |
|
428 | 428 | md5 = "b663300efdf421b3b727b19d7be9c7e7"; |
|
429 | 429 | }; |
|
430 | 430 | meta = { |
|
431 | 431 | license = [ pkgs.lib.licenses.asl20 ]; |
|
432 | 432 | }; |
|
433 | 433 | }; |
|
434 | 434 | bottle = super.buildPythonPackage { |
|
435 | 435 | name = "bottle-0.12.13"; |
|
436 | 436 | buildInputs = with self; []; |
|
437 | 437 | doCheck = false; |
|
438 | 438 | propagatedBuildInputs = with self; []; |
|
439 | 439 | src = fetchurl { |
|
440 | 440 | url = "https://pypi.python.org/packages/bd/99/04dc59ced52a8261ee0f965a8968717a255ea84a36013e527944dbf3468c/bottle-0.12.13.tar.gz"; |
|
441 | 441 | md5 = "d2fe1b48c1d49217e78bf326b1cad437"; |
|
442 | 442 | }; |
|
443 | 443 | meta = { |
|
444 | 444 | license = [ pkgs.lib.licenses.mit ]; |
|
445 | 445 | }; |
|
446 | 446 | }; |
|
447 | 447 | bumpversion = super.buildPythonPackage { |
|
448 | 448 | name = "bumpversion-0.5.3"; |
|
449 | 449 | buildInputs = with self; []; |
|
450 | 450 | doCheck = false; |
|
451 | 451 | propagatedBuildInputs = with self; []; |
|
452 | 452 | src = fetchurl { |
|
453 | 453 | url = "https://pypi.python.org/packages/14/41/8c9da3549f8e00c84f0432c3a8cf8ed6898374714676aab91501d48760db/bumpversion-0.5.3.tar.gz"; |
|
454 | 454 | md5 = "c66a3492eafcf5ad4b024be9fca29820"; |
|
455 | 455 | }; |
|
456 | 456 | meta = { |
|
457 | 457 | license = [ pkgs.lib.licenses.mit ]; |
|
458 | 458 | }; |
|
459 | 459 | }; |
|
460 | 460 | celery = super.buildPythonPackage { |
|
461 |
name = "celery- |
|
|
461 | name = "celery-4.1.0"; | |
|
462 | 462 | buildInputs = with self; []; |
|
463 | 463 | doCheck = false; |
|
464 |
propagatedBuildInputs = with self; [pyt |
|
|
464 | propagatedBuildInputs = with self; [pytz billiard kombu]; | |
|
465 | 465 | src = fetchurl { |
|
466 |
url = "https://pypi.python.org/packages/ |
|
|
467 | md5 = "898bc87e54f278055b561316ba73e222"; | |
|
466 | url = "https://pypi.python.org/packages/07/65/88a2a45fc80f487872c93121a701a53bbbc3d3d832016876fac84fc8d46a/celery-4.1.0.tar.gz"; | |
|
467 | md5 = "db91e1d26936381127f01e150fe3054a"; | |
|
468 | 468 | }; |
|
469 | 469 | meta = { |
|
470 | 470 | license = [ pkgs.lib.licenses.bsdOriginal ]; |
|
471 | 471 | }; |
|
472 | 472 | }; |
|
473 | 473 | channelstream = super.buildPythonPackage { |
|
474 | 474 | name = "channelstream-0.5.2"; |
|
475 | 475 | buildInputs = with self; []; |
|
476 | 476 | doCheck = false; |
|
477 | 477 | propagatedBuildInputs = with self; [gevent ws4py pyramid pyramid-jinja2 itsdangerous requests six]; |
|
478 | 478 | src = fetchurl { |
|
479 | 479 | url = "https://pypi.python.org/packages/2b/31/29a8e085cf5bf97fa88e7b947adabfc581a18a3463adf77fb6dada34a65f/channelstream-0.5.2.tar.gz"; |
|
480 | 480 | md5 = "1c5eb2a8a405be6f1073da94da6d81d3"; |
|
481 | 481 | }; |
|
482 | 482 | meta = { |
|
483 | 483 | license = [ pkgs.lib.licenses.bsdOriginal ]; |
|
484 | 484 | }; |
|
485 | 485 | }; |
|
486 | 486 | click = super.buildPythonPackage { |
|
487 | 487 | name = "click-6.6"; |
|
488 | 488 | buildInputs = with self; []; |
|
489 | 489 | doCheck = false; |
|
490 | 490 | propagatedBuildInputs = with self; []; |
|
491 | 491 | src = fetchurl { |
|
492 | 492 | url = "https://pypi.python.org/packages/7a/00/c14926d8232b36b08218067bcd5853caefb4737cda3f0a47437151344792/click-6.6.tar.gz"; |
|
493 | 493 | md5 = "d0b09582123605220ad6977175f3e51d"; |
|
494 | 494 | }; |
|
495 | 495 | meta = { |
|
496 | 496 | license = [ pkgs.lib.licenses.bsdOriginal ]; |
|
497 | 497 | }; |
|
498 | 498 | }; |
|
499 | 499 | colander = super.buildPythonPackage { |
|
500 | 500 | name = "colander-1.4"; |
|
501 | 501 | buildInputs = with self; []; |
|
502 | 502 | doCheck = false; |
|
503 | 503 | propagatedBuildInputs = with self; [translationstring iso8601]; |
|
504 | 504 | src = fetchurl { |
|
505 | 505 | url = "https://pypi.python.org/packages/cc/e2/c4e716ac4a426d8ad4dfe306c34f0018a22275d2420815784005bf771c84/colander-1.4.tar.gz"; |
|
506 | 506 | md5 = "cbb8e403c2ba05aeaa419d51fdb93736"; |
|
507 | 507 | }; |
|
508 | 508 | meta = { |
|
509 | 509 | license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ]; |
|
510 | 510 | }; |
|
511 | 511 | }; |
|
512 | 512 | configobj = super.buildPythonPackage { |
|
513 | 513 | name = "configobj-5.0.6"; |
|
514 | 514 | buildInputs = with self; []; |
|
515 | 515 | doCheck = false; |
|
516 | 516 | propagatedBuildInputs = with self; [six]; |
|
517 | 517 | src = fetchurl { |
|
518 | 518 | url = "https://pypi.python.org/packages/64/61/079eb60459c44929e684fa7d9e2fdca403f67d64dd9dbac27296be2e0fab/configobj-5.0.6.tar.gz"; |
|
519 | 519 | md5 = "e472a3a1c2a67bb0ec9b5d54c13a47d6"; |
|
520 | 520 | }; |
|
521 | 521 | meta = { |
|
522 | 522 | license = [ pkgs.lib.licenses.bsdOriginal ]; |
|
523 | 523 | }; |
|
524 | 524 | }; |
|
525 | 525 | configparser = super.buildPythonPackage { |
|
526 | 526 | name = "configparser-3.5.0"; |
|
527 | 527 | buildInputs = with self; []; |
|
528 | 528 | doCheck = false; |
|
529 | 529 | propagatedBuildInputs = with self; []; |
|
530 | 530 | src = fetchurl { |
|
531 | 531 | url = "https://pypi.python.org/packages/7c/69/c2ce7e91c89dc073eb1aa74c0621c3eefbffe8216b3f9af9d3885265c01c/configparser-3.5.0.tar.gz"; |
|
532 | 532 | md5 = "cfdd915a5b7a6c09917a64a573140538"; |
|
533 | 533 | }; |
|
534 | 534 | meta = { |
|
535 | 535 | license = [ pkgs.lib.licenses.mit ]; |
|
536 | 536 | }; |
|
537 | 537 | }; |
|
538 | 538 | cov-core = super.buildPythonPackage { |
|
539 | 539 | name = "cov-core-1.15.0"; |
|
540 | 540 | buildInputs = with self; []; |
|
541 | 541 | doCheck = false; |
|
542 | 542 | propagatedBuildInputs = with self; [coverage]; |
|
543 | 543 | src = fetchurl { |
|
544 | 544 | url = "https://pypi.python.org/packages/4b/87/13e75a47b4ba1be06f29f6d807ca99638bedc6b57fa491cd3de891ca2923/cov-core-1.15.0.tar.gz"; |
|
545 | 545 | md5 = "f519d4cb4c4e52856afb14af52919fe6"; |
|
546 | 546 | }; |
|
547 | 547 | meta = { |
|
548 | 548 | license = [ pkgs.lib.licenses.mit ]; |
|
549 | 549 | }; |
|
550 | 550 | }; |
|
551 | 551 | coverage = super.buildPythonPackage { |
|
552 | 552 | name = "coverage-3.7.1"; |
|
553 | 553 | buildInputs = with self; []; |
|
554 | 554 | doCheck = false; |
|
555 | 555 | propagatedBuildInputs = with self; []; |
|
556 | 556 | src = fetchurl { |
|
557 | 557 | url = "https://pypi.python.org/packages/09/4f/89b06c7fdc09687bca507dc411c342556ef9c5a3b26756137a4878ff19bf/coverage-3.7.1.tar.gz"; |
|
558 | 558 | md5 = "c47b36ceb17eaff3ecfab3bcd347d0df"; |
|
559 | 559 | }; |
|
560 | 560 | meta = { |
|
561 | 561 | license = [ pkgs.lib.licenses.bsdOriginal ]; |
|
562 | 562 | }; |
|
563 | 563 | }; |
|
564 | 564 | cssselect = super.buildPythonPackage { |
|
565 | 565 | name = "cssselect-1.0.1"; |
|
566 | 566 | buildInputs = with self; []; |
|
567 | 567 | doCheck = false; |
|
568 | 568 | propagatedBuildInputs = with self; []; |
|
569 | 569 | src = fetchurl { |
|
570 | 570 | url = "https://pypi.python.org/packages/77/ff/9c865275cd19290feba56344eba570e719efb7ca5b34d67ed12b22ebbb0d/cssselect-1.0.1.tar.gz"; |
|
571 | 571 | md5 = "3fa03bf82a9f0b1223c0f1eb1369e139"; |
|
572 | 572 | }; |
|
573 | 573 | meta = { |
|
574 | 574 | license = [ pkgs.lib.licenses.bsdOriginal ]; |
|
575 | 575 | }; |
|
576 | 576 | }; |
|
577 | 577 | decorator = super.buildPythonPackage { |
|
578 | 578 | name = "decorator-4.1.2"; |
|
579 | 579 | buildInputs = with self; []; |
|
580 | 580 | doCheck = false; |
|
581 | 581 | propagatedBuildInputs = with self; []; |
|
582 | 582 | src = fetchurl { |
|
583 | 583 | url = "https://pypi.python.org/packages/bb/e0/f6e41e9091e130bf16d4437dabbac3993908e4d6485ecbc985ef1352db94/decorator-4.1.2.tar.gz"; |
|
584 | 584 | md5 = "a0f7f4fe00ae2dde93494d90c192cf8c"; |
|
585 | 585 | }; |
|
586 | 586 | meta = { |
|
587 | 587 | license = [ pkgs.lib.licenses.bsdOriginal { fullName = "new BSD License"; } ]; |
|
588 | 588 | }; |
|
589 | 589 | }; |
|
590 | 590 | deform = super.buildPythonPackage { |
|
591 | 591 | name = "deform-2.0.4"; |
|
592 | 592 | buildInputs = with self; []; |
|
593 | 593 | doCheck = false; |
|
594 | 594 | propagatedBuildInputs = with self; [Chameleon colander iso8601 peppercorn translationstring zope.deprecation]; |
|
595 | 595 | src = fetchurl { |
|
596 | 596 | url = "https://pypi.python.org/packages/66/3b/eefcb07abcab7a97f6665aa2d0cf1af741d9d6e78a2e4657fd2b89f89880/deform-2.0.4.tar.gz"; |
|
597 | 597 | md5 = "34756e42cf50dd4b4430809116c4ec0a"; |
|
598 | 598 | }; |
|
599 | 599 | meta = { |
|
600 | 600 | license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ]; |
|
601 | 601 | }; |
|
602 | 602 | }; |
|
603 | 603 | docutils = super.buildPythonPackage { |
|
604 | 604 | name = "docutils-0.14"; |
|
605 | 605 | buildInputs = with self; []; |
|
606 | 606 | doCheck = false; |
|
607 | 607 | propagatedBuildInputs = with self; []; |
|
608 | 608 | src = fetchurl { |
|
609 | 609 | url = "https://pypi.python.org/packages/84/f4/5771e41fdf52aabebbadecc9381d11dea0fa34e4759b4071244fa094804c/docutils-0.14.tar.gz"; |
|
610 | 610 | md5 = "c53768d63db3873b7d452833553469de"; |
|
611 | 611 | }; |
|
612 | 612 | meta = { |
|
613 | 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 | 616 | dogpile.cache = super.buildPythonPackage { |
|
617 | 617 | name = "dogpile.cache-0.6.4"; |
|
618 | 618 | buildInputs = with self; []; |
|
619 | 619 | doCheck = false; |
|
620 | 620 | propagatedBuildInputs = with self; []; |
|
621 | 621 | src = fetchurl { |
|
622 | 622 | url = "https://pypi.python.org/packages/b6/3d/35c05ca01c070bb70d9d422f2c4858ecb021b05b21af438fec5ccd7b945c/dogpile.cache-0.6.4.tar.gz"; |
|
623 | 623 | md5 = "66e0a6cae6c08cb1ea25f89d0eadfeb0"; |
|
624 | 624 | }; |
|
625 | 625 | meta = { |
|
626 | 626 | license = [ pkgs.lib.licenses.bsdOriginal ]; |
|
627 | 627 | }; |
|
628 | 628 | }; |
|
629 | 629 | dogpile.core = super.buildPythonPackage { |
|
630 | 630 | name = "dogpile.core-0.4.1"; |
|
631 | 631 | buildInputs = with self; []; |
|
632 | 632 | doCheck = false; |
|
633 | 633 | propagatedBuildInputs = with self; []; |
|
634 | 634 | src = fetchurl { |
|
635 | 635 | url = "https://pypi.python.org/packages/0e/77/e72abc04c22aedf874301861e5c1e761231c288b5de369c18be8f4b5c9bb/dogpile.core-0.4.1.tar.gz"; |
|
636 | 636 | md5 = "01cb19f52bba3e95c9b560f39341f045"; |
|
637 | 637 | }; |
|
638 | 638 | meta = { |
|
639 | 639 | license = [ pkgs.lib.licenses.bsdOriginal ]; |
|
640 | 640 | }; |
|
641 | 641 | }; |
|
642 | 642 | ecdsa = super.buildPythonPackage { |
|
643 | 643 | name = "ecdsa-0.13"; |
|
644 | 644 | buildInputs = with self; []; |
|
645 | 645 | doCheck = false; |
|
646 | 646 | propagatedBuildInputs = with self; []; |
|
647 | 647 | src = fetchurl { |
|
648 | 648 | url = "https://pypi.python.org/packages/f9/e5/99ebb176e47f150ac115ffeda5fedb6a3dbb3c00c74a59fd84ddf12f5857/ecdsa-0.13.tar.gz"; |
|
649 | 649 | md5 = "1f60eda9cb5c46722856db41a3ae6670"; |
|
650 | 650 | }; |
|
651 | 651 | meta = { |
|
652 | 652 | license = [ pkgs.lib.licenses.mit ]; |
|
653 | 653 | }; |
|
654 | 654 | }; |
|
655 | 655 | elasticsearch = super.buildPythonPackage { |
|
656 | 656 | name = "elasticsearch-2.3.0"; |
|
657 | 657 | buildInputs = with self; []; |
|
658 | 658 | doCheck = false; |
|
659 | 659 | propagatedBuildInputs = with self; [urllib3]; |
|
660 | 660 | src = fetchurl { |
|
661 | 661 | url = "https://pypi.python.org/packages/10/35/5fd52c5f0b0ee405ed4b5195e8bce44c5e041787680dc7b94b8071cac600/elasticsearch-2.3.0.tar.gz"; |
|
662 | 662 | md5 = "2550f3b51629cf1ef9636608af92c340"; |
|
663 | 663 | }; |
|
664 | 664 | meta = { |
|
665 | 665 | license = [ pkgs.lib.licenses.asl20 ]; |
|
666 | 666 | }; |
|
667 | 667 | }; |
|
668 | 668 | elasticsearch-dsl = super.buildPythonPackage { |
|
669 | 669 | name = "elasticsearch-dsl-2.2.0"; |
|
670 | 670 | buildInputs = with self; []; |
|
671 | 671 | doCheck = false; |
|
672 | 672 | propagatedBuildInputs = with self; [six python-dateutil elasticsearch]; |
|
673 | 673 | src = fetchurl { |
|
674 | 674 | url = "https://pypi.python.org/packages/66/2f/52a086968788e58461641570f45c3207a52d46ebbe9b77dc22b6a8ffda66/elasticsearch-dsl-2.2.0.tar.gz"; |
|
675 | 675 | md5 = "fa6bd3c87ea3caa8f0f051bc37c53221"; |
|
676 | 676 | }; |
|
677 | 677 | meta = { |
|
678 | 678 | license = [ pkgs.lib.licenses.asl20 ]; |
|
679 | 679 | }; |
|
680 | 680 | }; |
|
681 | 681 | entrypoints = super.buildPythonPackage { |
|
682 | 682 | name = "entrypoints-0.2.2"; |
|
683 | 683 | buildInputs = with self; []; |
|
684 | 684 | doCheck = false; |
|
685 | 685 | propagatedBuildInputs = with self; [configparser]; |
|
686 | 686 | src = fetchurl { |
|
687 | 687 | url = "https://code.rhodecode.com/upstream/entrypoints/archive/96e6d645684e1af3d7df5b5272f3fe85a546b233.tar.gz?md5=7db37771aea9ac9fefe093e5d6987313"; |
|
688 | 688 | md5 = "7db37771aea9ac9fefe093e5d6987313"; |
|
689 | 689 | }; |
|
690 | 690 | meta = { |
|
691 | 691 | license = [ pkgs.lib.licenses.mit ]; |
|
692 | 692 | }; |
|
693 | 693 | }; |
|
694 | 694 | enum34 = super.buildPythonPackage { |
|
695 | 695 | name = "enum34-1.1.6"; |
|
696 | 696 | buildInputs = with self; []; |
|
697 | 697 | doCheck = false; |
|
698 | 698 | propagatedBuildInputs = with self; []; |
|
699 | 699 | src = fetchurl { |
|
700 | 700 | url = "https://pypi.python.org/packages/bf/3e/31d502c25302814a7c2f1d3959d2a3b3f78e509002ba91aea64993936876/enum34-1.1.6.tar.gz"; |
|
701 | 701 | md5 = "5f13a0841a61f7fc295c514490d120d0"; |
|
702 | 702 | }; |
|
703 | 703 | meta = { |
|
704 | 704 | license = [ pkgs.lib.licenses.bsdOriginal ]; |
|
705 | 705 | }; |
|
706 | 706 | }; |
|
707 | 707 | funcsigs = super.buildPythonPackage { |
|
708 | 708 | name = "funcsigs-1.0.2"; |
|
709 | 709 | buildInputs = with self; []; |
|
710 | 710 | doCheck = false; |
|
711 | 711 | propagatedBuildInputs = with self; []; |
|
712 | 712 | src = fetchurl { |
|
713 | 713 | url = "https://pypi.python.org/packages/94/4a/db842e7a0545de1cdb0439bb80e6e42dfe82aaeaadd4072f2263a4fbed23/funcsigs-1.0.2.tar.gz"; |
|
714 | 714 | md5 = "7e583285b1fb8a76305d6d68f4ccc14e"; |
|
715 | 715 | }; |
|
716 | 716 | meta = { |
|
717 | 717 | license = [ { fullName = "ASL"; } pkgs.lib.licenses.asl20 ]; |
|
718 | 718 | }; |
|
719 | 719 | }; |
|
720 | 720 | functools32 = super.buildPythonPackage { |
|
721 | 721 | name = "functools32-3.2.3.post2"; |
|
722 | 722 | buildInputs = with self; []; |
|
723 | 723 | doCheck = false; |
|
724 | 724 | propagatedBuildInputs = with self; []; |
|
725 | 725 | src = fetchurl { |
|
726 | 726 | url = "https://pypi.python.org/packages/5e/1a/0aa2c8195a204a9f51284018562dea77e25511f02fe924fac202fc012172/functools32-3.2.3-2.zip"; |
|
727 | 727 | md5 = "d55232eb132ec779e6893c902a0bc5ad"; |
|
728 | 728 | }; |
|
729 | 729 | meta = { |
|
730 | 730 | license = [ pkgs.lib.licenses.psfl ]; |
|
731 | 731 | }; |
|
732 | 732 | }; |
|
733 | 733 | future = super.buildPythonPackage { |
|
734 | 734 | name = "future-0.14.3"; |
|
735 | 735 | buildInputs = with self; []; |
|
736 | 736 | doCheck = false; |
|
737 | 737 | propagatedBuildInputs = with self; []; |
|
738 | 738 | src = fetchurl { |
|
739 | 739 | url = "https://pypi.python.org/packages/83/80/8ef3a11a15f8eaafafa0937b20c1b3f73527e69ab6b3fa1cf94a5a96aabb/future-0.14.3.tar.gz"; |
|
740 | 740 | md5 = "e94079b0bd1fc054929e8769fc0f6083"; |
|
741 | 741 | }; |
|
742 | 742 | meta = { |
|
743 | 743 | license = [ { fullName = "OSI Approved"; } pkgs.lib.licenses.mit ]; |
|
744 | 744 | }; |
|
745 | 745 | }; |
|
746 | 746 | futures = super.buildPythonPackage { |
|
747 | 747 | name = "futures-3.0.2"; |
|
748 | 748 | buildInputs = with self; []; |
|
749 | 749 | doCheck = false; |
|
750 | 750 | propagatedBuildInputs = with self; []; |
|
751 | 751 | src = fetchurl { |
|
752 | 752 | url = "https://pypi.python.org/packages/f8/e7/fc0fcbeb9193ba2d4de00b065e7fd5aecd0679e93ce95a07322b2b1434f4/futures-3.0.2.tar.gz"; |
|
753 | 753 | md5 = "42aaf1e4de48d6e871d77dc1f9d96d5a"; |
|
754 | 754 | }; |
|
755 | 755 | meta = { |
|
756 | 756 | license = [ pkgs.lib.licenses.bsdOriginal ]; |
|
757 | 757 | }; |
|
758 | 758 | }; |
|
759 | 759 | gevent = super.buildPythonPackage { |
|
760 | 760 | name = "gevent-1.2.2"; |
|
761 | 761 | buildInputs = with self; []; |
|
762 | 762 | doCheck = false; |
|
763 | 763 | propagatedBuildInputs = with self; [greenlet]; |
|
764 | 764 | src = fetchurl { |
|
765 | 765 | url = "https://pypi.python.org/packages/1b/92/b111f76e54d2be11375b47b213b56687214f258fd9dae703546d30b837be/gevent-1.2.2.tar.gz"; |
|
766 | 766 | md5 = "7f0baf355384fe5ff2ecf66853422554"; |
|
767 | 767 | }; |
|
768 | 768 | meta = { |
|
769 | 769 | license = [ pkgs.lib.licenses.mit ]; |
|
770 | 770 | }; |
|
771 | 771 | }; |
|
772 | 772 | gnureadline = super.buildPythonPackage { |
|
773 | 773 | name = "gnureadline-6.3.8"; |
|
774 | 774 | buildInputs = with self; []; |
|
775 | 775 | doCheck = false; |
|
776 | 776 | propagatedBuildInputs = with self; []; |
|
777 | 777 | src = fetchurl { |
|
778 | 778 | url = "https://pypi.python.org/packages/50/64/86085c823cd78f9df9d8e33dce0baa71618016f8860460b82cf6610e1eb3/gnureadline-6.3.8.tar.gz"; |
|
779 | 779 | md5 = "ba341f4b907250bd1f47dbc06290604f"; |
|
780 | 780 | }; |
|
781 | 781 | meta = { |
|
782 | 782 | license = [ { fullName = "GNU General Public License v3 (GPLv3)"; } pkgs.lib.licenses.gpl1 ]; |
|
783 | 783 | }; |
|
784 | 784 | }; |
|
785 | 785 | gprof2dot = super.buildPythonPackage { |
|
786 | 786 | name = "gprof2dot-2017.9.19"; |
|
787 | 787 | buildInputs = with self; []; |
|
788 | 788 | doCheck = false; |
|
789 | 789 | propagatedBuildInputs = with self; []; |
|
790 | 790 | src = fetchurl { |
|
791 | 791 | url = "https://pypi.python.org/packages/9d/36/f977122502979f3dfb50704979c9ed70e6b620787942b089bf1af15f5aba/gprof2dot-2017.9.19.tar.gz"; |
|
792 | 792 | md5 = "cda2d552bb0d0b9f16e6824a9aabd225"; |
|
793 | 793 | }; |
|
794 | 794 | meta = { |
|
795 | 795 | license = [ { fullName = "GNU Lesser General Public License v3 or later (LGPLv3+)"; } { fullName = "LGPL"; } ]; |
|
796 | 796 | }; |
|
797 | 797 | }; |
|
798 | 798 | graphviz = super.buildPythonPackage { |
|
799 | 799 | name = "graphviz-0.8.1"; |
|
800 | 800 | buildInputs = with self; []; |
|
801 | 801 | doCheck = false; |
|
802 | 802 | propagatedBuildInputs = with self; []; |
|
803 | 803 | src = fetchurl { |
|
804 | 804 | url = "https://pypi.python.org/packages/a9/a6/ee6721349489a2da6eedd3dba124f2b5ac15ee1e0a7bd4d3cfdc4fff0327/graphviz-0.8.1.zip"; |
|
805 | 805 | md5 = "88d8efa88c02a735b3659fe0feaf0b96"; |
|
806 | 806 | }; |
|
807 | 807 | meta = { |
|
808 | 808 | license = [ pkgs.lib.licenses.mit ]; |
|
809 | 809 | }; |
|
810 | 810 | }; |
|
811 | 811 | greenlet = super.buildPythonPackage { |
|
812 | 812 | name = "greenlet-0.4.12"; |
|
813 | 813 | buildInputs = with self; []; |
|
814 | 814 | doCheck = false; |
|
815 | 815 | propagatedBuildInputs = with self; []; |
|
816 | 816 | src = fetchurl { |
|
817 | 817 | url = "https://pypi.python.org/packages/be/76/82af375d98724054b7e273b5d9369346937324f9bcc20980b45b068ef0b0/greenlet-0.4.12.tar.gz"; |
|
818 | 818 | md5 = "e8637647d58a26c4a1f51ca393e53c00"; |
|
819 | 819 | }; |
|
820 | 820 | meta = { |
|
821 | 821 | license = [ pkgs.lib.licenses.mit ]; |
|
822 | 822 | }; |
|
823 | 823 | }; |
|
824 | 824 | gunicorn = super.buildPythonPackage { |
|
825 | 825 | name = "gunicorn-19.7.1"; |
|
826 | 826 | buildInputs = with self; []; |
|
827 | 827 | doCheck = false; |
|
828 | 828 | propagatedBuildInputs = with self; []; |
|
829 | 829 | src = fetchurl { |
|
830 | 830 | url = "https://pypi.python.org/packages/30/3a/10bb213cede0cc4d13ac2263316c872a64bf4c819000c8ccd801f1d5f822/gunicorn-19.7.1.tar.gz"; |
|
831 | 831 | md5 = "174d3c3cd670a5be0404d84c484e590c"; |
|
832 | 832 | }; |
|
833 | 833 | meta = { |
|
834 | 834 | license = [ pkgs.lib.licenses.mit ]; |
|
835 | 835 | }; |
|
836 | 836 | }; |
|
837 | 837 | html5lib = super.buildPythonPackage { |
|
838 | 838 | name = "html5lib-0.9999999"; |
|
839 | 839 | buildInputs = with self; []; |
|
840 | 840 | doCheck = false; |
|
841 | 841 | propagatedBuildInputs = with self; [six]; |
|
842 | 842 | src = fetchurl { |
|
843 | 843 | url = "https://pypi.python.org/packages/ae/ae/bcb60402c60932b32dfaf19bb53870b29eda2cd17551ba5639219fb5ebf9/html5lib-0.9999999.tar.gz"; |
|
844 | 844 | md5 = "ef43cb05e9e799f25d65d1135838a96f"; |
|
845 | 845 | }; |
|
846 | 846 | meta = { |
|
847 | 847 | license = [ pkgs.lib.licenses.mit ]; |
|
848 | 848 | }; |
|
849 | 849 | }; |
|
850 | 850 | hupper = super.buildPythonPackage { |
|
851 | 851 | name = "hupper-1.0"; |
|
852 | 852 | buildInputs = with self; []; |
|
853 | 853 | doCheck = false; |
|
854 | 854 | propagatedBuildInputs = with self; []; |
|
855 | 855 | src = fetchurl { |
|
856 | 856 | url = "https://pypi.python.org/packages/2e/07/df892c564dc09bb3cf6f6deb976c26adf9117db75ba218cb4353dbc9d826/hupper-1.0.tar.gz"; |
|
857 | 857 | md5 = "26e77da7d5ac5858f59af050d1a6eb5a"; |
|
858 | 858 | }; |
|
859 | 859 | meta = { |
|
860 | 860 | license = [ pkgs.lib.licenses.mit ]; |
|
861 | 861 | }; |
|
862 | 862 | }; |
|
863 | 863 | infrae.cache = super.buildPythonPackage { |
|
864 | 864 | name = "infrae.cache-1.0.1"; |
|
865 | 865 | buildInputs = with self; []; |
|
866 | 866 | doCheck = false; |
|
867 | 867 | propagatedBuildInputs = with self; [Beaker repoze.lru]; |
|
868 | 868 | src = fetchurl { |
|
869 | 869 | url = "https://pypi.python.org/packages/bb/f0/e7d5e984cf6592fd2807dc7bc44a93f9d18e04e6a61f87fdfb2622422d74/infrae.cache-1.0.1.tar.gz"; |
|
870 | 870 | md5 = "b09076a766747e6ed2a755cc62088e32"; |
|
871 | 871 | }; |
|
872 | 872 | meta = { |
|
873 | 873 | license = [ pkgs.lib.licenses.zpt21 ]; |
|
874 | 874 | }; |
|
875 | 875 | }; |
|
876 | 876 | invoke = super.buildPythonPackage { |
|
877 | 877 | name = "invoke-0.13.0"; |
|
878 | 878 | buildInputs = with self; []; |
|
879 | 879 | doCheck = false; |
|
880 | 880 | propagatedBuildInputs = with self; []; |
|
881 | 881 | src = fetchurl { |
|
882 | 882 | url = "https://pypi.python.org/packages/47/bf/d07ef52fa1ac645468858bbac7cb95b246a972a045e821493d17d89c81be/invoke-0.13.0.tar.gz"; |
|
883 | 883 | md5 = "c0d1ed4bfb34eaab551662d8cfee6540"; |
|
884 | 884 | }; |
|
885 | 885 | meta = { |
|
886 | 886 | license = [ pkgs.lib.licenses.bsdOriginal ]; |
|
887 | 887 | }; |
|
888 | 888 | }; |
|
889 | 889 | ipaddress = super.buildPythonPackage { |
|
890 | 890 | name = "ipaddress-1.0.18"; |
|
891 | 891 | buildInputs = with self; []; |
|
892 | 892 | doCheck = false; |
|
893 | 893 | propagatedBuildInputs = with self; []; |
|
894 | 894 | src = fetchurl { |
|
895 | 895 | url = "https://pypi.python.org/packages/4e/13/774faf38b445d0b3a844b65747175b2e0500164b7c28d78e34987a5bfe06/ipaddress-1.0.18.tar.gz"; |
|
896 | 896 | md5 = "310c2dfd64eb6f0df44aa8c59f2334a7"; |
|
897 | 897 | }; |
|
898 | 898 | meta = { |
|
899 | 899 | license = [ pkgs.lib.licenses.psfl ]; |
|
900 | 900 | }; |
|
901 | 901 | }; |
|
902 | 902 | ipdb = super.buildPythonPackage { |
|
903 | 903 | name = "ipdb-0.10.3"; |
|
904 | 904 | buildInputs = with self; []; |
|
905 | 905 | doCheck = false; |
|
906 | 906 | propagatedBuildInputs = with self; [setuptools ipython]; |
|
907 | 907 | src = fetchurl { |
|
908 | 908 | url = "https://pypi.python.org/packages/ad/cc/0e7298e1fbf2efd52667c9354a12aa69fb6f796ce230cca03525051718ef/ipdb-0.10.3.tar.gz"; |
|
909 | 909 | md5 = "def1f6ac075d54bdee07e6501263d4fa"; |
|
910 | 910 | }; |
|
911 | 911 | meta = { |
|
912 | 912 | license = [ pkgs.lib.licenses.bsdOriginal ]; |
|
913 | 913 | }; |
|
914 | 914 | }; |
|
915 | 915 | ipython = super.buildPythonPackage { |
|
916 | 916 | name = "ipython-5.1.0"; |
|
917 | 917 | buildInputs = with self; []; |
|
918 | 918 | doCheck = false; |
|
919 | 919 | propagatedBuildInputs = with self; [setuptools decorator pickleshare simplegeneric traitlets prompt-toolkit Pygments pexpect backports.shutil-get-terminal-size pathlib2 pexpect]; |
|
920 | 920 | src = fetchurl { |
|
921 | 921 | url = "https://pypi.python.org/packages/89/63/a9292f7cd9d0090a0f995e1167f3f17d5889dcbc9a175261719c513b9848/ipython-5.1.0.tar.gz"; |
|
922 | 922 | md5 = "47c8122420f65b58784cb4b9b4af35e3"; |
|
923 | 923 | }; |
|
924 | 924 | meta = { |
|
925 | 925 | license = [ pkgs.lib.licenses.bsdOriginal ]; |
|
926 | 926 | }; |
|
927 | 927 | }; |
|
928 | 928 | ipython-genutils = super.buildPythonPackage { |
|
929 | 929 | name = "ipython-genutils-0.2.0"; |
|
930 | 930 | buildInputs = with self; []; |
|
931 | 931 | doCheck = false; |
|
932 | 932 | propagatedBuildInputs = with self; []; |
|
933 | 933 | src = fetchurl { |
|
934 | 934 | url = "https://pypi.python.org/packages/e8/69/fbeffffc05236398ebfcfb512b6d2511c622871dca1746361006da310399/ipython_genutils-0.2.0.tar.gz"; |
|
935 | 935 | md5 = "5a4f9781f78466da0ea1a648f3e1f79f"; |
|
936 | 936 | }; |
|
937 | 937 | meta = { |
|
938 | 938 | license = [ pkgs.lib.licenses.bsdOriginal ]; |
|
939 | 939 | }; |
|
940 | 940 | }; |
|
941 | 941 | iso8601 = super.buildPythonPackage { |
|
942 | 942 | name = "iso8601-0.1.12"; |
|
943 | 943 | buildInputs = with self; []; |
|
944 | 944 | doCheck = false; |
|
945 | 945 | propagatedBuildInputs = with self; []; |
|
946 | 946 | src = fetchurl { |
|
947 | 947 | url = "https://pypi.python.org/packages/45/13/3db24895497345fb44c4248c08b16da34a9eb02643cea2754b21b5ed08b0/iso8601-0.1.12.tar.gz"; |
|
948 | 948 | md5 = "4de940f691c5ea759fb254384c8ddcf6"; |
|
949 | 949 | }; |
|
950 | 950 | meta = { |
|
951 | 951 | license = [ pkgs.lib.licenses.mit ]; |
|
952 | 952 | }; |
|
953 | 953 | }; |
|
954 | 954 | itsdangerous = super.buildPythonPackage { |
|
955 | 955 | name = "itsdangerous-0.24"; |
|
956 | 956 | buildInputs = with self; []; |
|
957 | 957 | doCheck = false; |
|
958 | 958 | propagatedBuildInputs = with self; []; |
|
959 | 959 | src = fetchurl { |
|
960 | 960 | url = "https://pypi.python.org/packages/dc/b4/a60bcdba945c00f6d608d8975131ab3f25b22f2bcfe1dab221165194b2d4/itsdangerous-0.24.tar.gz"; |
|
961 | 961 | md5 = "a3d55aa79369aef5345c036a8a26307f"; |
|
962 | 962 | }; |
|
963 | 963 | meta = { |
|
964 | 964 | license = [ pkgs.lib.licenses.bsdOriginal ]; |
|
965 | 965 | }; |
|
966 | 966 | }; |
|
967 | 967 | jsonschema = super.buildPythonPackage { |
|
968 | 968 | name = "jsonschema-2.6.0"; |
|
969 | 969 | buildInputs = with self; []; |
|
970 | 970 | doCheck = false; |
|
971 | 971 | propagatedBuildInputs = with self; [functools32]; |
|
972 | 972 | src = fetchurl { |
|
973 | 973 | url = "https://pypi.python.org/packages/58/b9/171dbb07e18c6346090a37f03c7e74410a1a56123f847efed59af260a298/jsonschema-2.6.0.tar.gz"; |
|
974 | 974 | md5 = "50c6b69a373a8b55ff1e0ec6e78f13f4"; |
|
975 | 975 | }; |
|
976 | 976 | meta = { |
|
977 | 977 | license = [ pkgs.lib.licenses.mit ]; |
|
978 | 978 | }; |
|
979 | 979 | }; |
|
980 | 980 | jupyter-client = super.buildPythonPackage { |
|
981 | 981 | name = "jupyter-client-5.0.0"; |
|
982 | 982 | buildInputs = with self; []; |
|
983 | 983 | doCheck = false; |
|
984 | 984 | propagatedBuildInputs = with self; [traitlets jupyter-core pyzmq python-dateutil]; |
|
985 | 985 | src = fetchurl { |
|
986 | 986 | url = "https://pypi.python.org/packages/e5/6f/65412ed462202b90134b7e761b0b7e7f949e07a549c1755475333727b3d0/jupyter_client-5.0.0.tar.gz"; |
|
987 | 987 | md5 = "1acd331b5c9fb4d79dae9939e79f2426"; |
|
988 | 988 | }; |
|
989 | 989 | meta = { |
|
990 | 990 | license = [ pkgs.lib.licenses.bsdOriginal ]; |
|
991 | 991 | }; |
|
992 | 992 | }; |
|
993 | 993 | jupyter-core = super.buildPythonPackage { |
|
994 | 994 | name = "jupyter-core-4.4.0"; |
|
995 | 995 | buildInputs = with self; []; |
|
996 | 996 | doCheck = false; |
|
997 | 997 | propagatedBuildInputs = with self; [traitlets]; |
|
998 | 998 | src = fetchurl { |
|
999 | 999 | url = "https://pypi.python.org/packages/b6/2d/2804f4de3a95583f65e5dcb4d7c8c7183124882323758996e867f47e72af/jupyter_core-4.4.0.tar.gz"; |
|
1000 | 1000 | md5 = "7829fc07884ed98459e170f217e2a5ba"; |
|
1001 | 1001 | }; |
|
1002 | 1002 | meta = { |
|
1003 | 1003 | license = [ pkgs.lib.licenses.bsdOriginal ]; |
|
1004 | 1004 | }; |
|
1005 | 1005 | }; |
|
1006 | 1006 | kombu = super.buildPythonPackage { |
|
1007 |
name = "kombu-1. |
|
|
1007 | name = "kombu-4.1.0"; | |
|
1008 | 1008 | buildInputs = with self; []; |
|
1009 | 1009 | doCheck = false; |
|
1010 |
propagatedBuildInputs = with self; [ |
|
|
1010 | propagatedBuildInputs = with self; [amqp]; | |
|
1011 | 1011 | src = fetchurl { |
|
1012 |
url = "https://pypi.python.org/packages/ |
|
|
1013 | md5 = "50662f3c7e9395b3d0721fb75d100b63"; | |
|
1012 | url = "https://pypi.python.org/packages/03/5e/1a47d1e543d4943d65330af4e4406049f443878818fb65bfdc651bb93a96/kombu-4.1.0.tar.gz"; | |
|
1013 | md5 = "2fb2be9fec0e6514231bba23a3779439"; | |
|
1014 | 1014 | }; |
|
1015 | 1015 | meta = { |
|
1016 | 1016 | license = [ pkgs.lib.licenses.bsdOriginal ]; |
|
1017 | 1017 | }; |
|
1018 | 1018 | }; |
|
1019 | 1019 | lxml = super.buildPythonPackage { |
|
1020 | 1020 | name = "lxml-3.7.3"; |
|
1021 | 1021 | buildInputs = with self; []; |
|
1022 | 1022 | doCheck = false; |
|
1023 | 1023 | propagatedBuildInputs = with self; []; |
|
1024 | 1024 | src = fetchurl { |
|
1025 | 1025 | url = "https://pypi.python.org/packages/39/e8/a8e0b1fa65dd021d48fe21464f71783655f39a41f218293c1c590d54eb82/lxml-3.7.3.tar.gz"; |
|
1026 | 1026 | md5 = "075692ce442e69bbd604d44e21c02753"; |
|
1027 | 1027 | }; |
|
1028 | 1028 | meta = { |
|
1029 | 1029 | license = [ pkgs.lib.licenses.bsdOriginal ]; |
|
1030 | 1030 | }; |
|
1031 | 1031 | }; |
|
1032 | 1032 | mistune = super.buildPythonPackage { |
|
1033 | 1033 | name = "mistune-0.8.1"; |
|
1034 | 1034 | buildInputs = with self; []; |
|
1035 | 1035 | doCheck = false; |
|
1036 | 1036 | propagatedBuildInputs = with self; []; |
|
1037 | 1037 | src = fetchurl { |
|
1038 | 1038 | url = "https://pypi.python.org/packages/d6/c6/a79d71f6245a8c409a6db3ca2cb86ac657f34b3cfc92b9549b7a9489bfe0/mistune-0.8.1.tar.gz"; |
|
1039 | 1039 | md5 = "0fba2b3858a529fc6df675dc7d534bf4"; |
|
1040 | 1040 | }; |
|
1041 | 1041 | meta = { |
|
1042 | 1042 | license = [ pkgs.lib.licenses.bsdOriginal ]; |
|
1043 | 1043 | }; |
|
1044 | 1044 | }; |
|
1045 | 1045 | mock = super.buildPythonPackage { |
|
1046 | 1046 | name = "mock-1.0.1"; |
|
1047 | 1047 | buildInputs = with self; []; |
|
1048 | 1048 | doCheck = false; |
|
1049 | 1049 | propagatedBuildInputs = with self; []; |
|
1050 | 1050 | src = fetchurl { |
|
1051 | 1051 | url = "https://pypi.python.org/packages/15/45/30273ee91feb60dabb8fbb2da7868520525f02cf910279b3047182feed80/mock-1.0.1.zip"; |
|
1052 | 1052 | md5 = "869f08d003c289a97c1a6610faf5e913"; |
|
1053 | 1053 | }; |
|
1054 | 1054 | meta = { |
|
1055 | 1055 | license = [ pkgs.lib.licenses.bsdOriginal ]; |
|
1056 | 1056 | }; |
|
1057 | 1057 | }; |
|
1058 | 1058 | msgpack-python = super.buildPythonPackage { |
|
1059 | 1059 | name = "msgpack-python-0.4.8"; |
|
1060 | 1060 | buildInputs = with self; []; |
|
1061 | 1061 | doCheck = false; |
|
1062 | 1062 | propagatedBuildInputs = with self; []; |
|
1063 | 1063 | src = fetchurl { |
|
1064 | 1064 | url = "https://pypi.python.org/packages/21/27/8a1d82041c7a2a51fcc73675875a5f9ea06c2663e02fcfeb708be1d081a0/msgpack-python-0.4.8.tar.gz"; |
|
1065 | 1065 | md5 = "dcd854fb41ee7584ebbf35e049e6be98"; |
|
1066 | 1066 | }; |
|
1067 | 1067 | meta = { |
|
1068 | 1068 | license = [ pkgs.lib.licenses.asl20 ]; |
|
1069 | 1069 | }; |
|
1070 | 1070 | }; |
|
1071 | 1071 | nbconvert = super.buildPythonPackage { |
|
1072 | 1072 | name = "nbconvert-5.1.1"; |
|
1073 | 1073 | buildInputs = with self; []; |
|
1074 | 1074 | doCheck = false; |
|
1075 | 1075 | propagatedBuildInputs = with self; [mistune Jinja2 Pygments traitlets jupyter-core nbformat entrypoints bleach pandocfilters testpath]; |
|
1076 | 1076 | src = fetchurl { |
|
1077 | 1077 | url = "https://pypi.python.org/packages/95/58/df1c91f1658ee5df19097f915a1e71c91fc824a708d82d2b2e35f8b80e9a/nbconvert-5.1.1.tar.gz"; |
|
1078 | 1078 | md5 = "d0263fb03a44db2f94eea09a608ed813"; |
|
1079 | 1079 | }; |
|
1080 | 1080 | meta = { |
|
1081 | 1081 | license = [ pkgs.lib.licenses.bsdOriginal ]; |
|
1082 | 1082 | }; |
|
1083 | 1083 | }; |
|
1084 | 1084 | nbformat = super.buildPythonPackage { |
|
1085 | 1085 | name = "nbformat-4.3.0"; |
|
1086 | 1086 | buildInputs = with self; []; |
|
1087 | 1087 | doCheck = false; |
|
1088 | 1088 | propagatedBuildInputs = with self; [ipython-genutils traitlets jsonschema jupyter-core]; |
|
1089 | 1089 | src = fetchurl { |
|
1090 | 1090 | url = "https://pypi.python.org/packages/f9/c5/89df4abf906f766727f976e170caa85b4f1c1d1feb1f45d716016e68e19f/nbformat-4.3.0.tar.gz"; |
|
1091 | 1091 | md5 = "9a00d20425914cd5ba5f97769d9963ca"; |
|
1092 | 1092 | }; |
|
1093 | 1093 | meta = { |
|
1094 | 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 | 1097 | objgraph = super.buildPythonPackage { |
|
1111 | 1098 | name = "objgraph-3.1.1"; |
|
1112 | 1099 | buildInputs = with self; []; |
|
1113 | 1100 | doCheck = false; |
|
1114 | 1101 | propagatedBuildInputs = with self; [graphviz]; |
|
1115 | 1102 | src = fetchurl { |
|
1116 | 1103 | url = "https://pypi.python.org/packages/be/58/9ca81a20cc837054e94866df1475d899caaa94f3732b8a46006858b015f7/objgraph-3.1.1.tar.gz"; |
|
1117 | 1104 | md5 = "253af9944763377877c3678d8aaebb8b"; |
|
1118 | 1105 | }; |
|
1119 | 1106 | meta = { |
|
1120 | 1107 | license = [ pkgs.lib.licenses.mit ]; |
|
1121 | 1108 | }; |
|
1122 | 1109 | }; |
|
1123 | 1110 | packaging = super.buildPythonPackage { |
|
1124 | 1111 | name = "packaging-15.2"; |
|
1125 | 1112 | buildInputs = with self; []; |
|
1126 | 1113 | doCheck = false; |
|
1127 | 1114 | propagatedBuildInputs = with self; []; |
|
1128 | 1115 | src = fetchurl { |
|
1129 | 1116 | url = "https://pypi.python.org/packages/24/c4/185da1304f07047dc9e0c46c31db75c0351bd73458ac3efad7da3dbcfbe1/packaging-15.2.tar.gz"; |
|
1130 | 1117 | md5 = "c16093476f6ced42128bf610e5db3784"; |
|
1131 | 1118 | }; |
|
1132 | 1119 | meta = { |
|
1133 | 1120 | license = [ pkgs.lib.licenses.asl20 ]; |
|
1134 | 1121 | }; |
|
1135 | 1122 | }; |
|
1136 | 1123 | pandocfilters = super.buildPythonPackage { |
|
1137 | 1124 | name = "pandocfilters-1.4.2"; |
|
1138 | 1125 | buildInputs = with self; []; |
|
1139 | 1126 | doCheck = false; |
|
1140 | 1127 | propagatedBuildInputs = with self; []; |
|
1141 | 1128 | src = fetchurl { |
|
1142 | 1129 | url = "https://pypi.python.org/packages/4c/ea/236e2584af67bb6df960832731a6e5325fd4441de001767da328c33368ce/pandocfilters-1.4.2.tar.gz"; |
|
1143 | 1130 | md5 = "dc391791ef54c7de1572d7b46b63361f"; |
|
1144 | 1131 | }; |
|
1145 | 1132 | meta = { |
|
1146 | 1133 | license = [ pkgs.lib.licenses.bsdOriginal ]; |
|
1147 | 1134 | }; |
|
1148 | 1135 | }; |
|
1149 | 1136 | pathlib2 = super.buildPythonPackage { |
|
1150 | 1137 | name = "pathlib2-2.3.0"; |
|
1151 | 1138 | buildInputs = with self; []; |
|
1152 | 1139 | doCheck = false; |
|
1153 | 1140 | propagatedBuildInputs = with self; [six scandir]; |
|
1154 | 1141 | src = fetchurl { |
|
1155 | 1142 | url = "https://pypi.python.org/packages/a1/14/df0deb867c2733f7d857523c10942b3d6612a1b222502fdffa9439943dfb/pathlib2-2.3.0.tar.gz"; |
|
1156 | 1143 | md5 = "89c90409d11fd5947966b6a30a47d18c"; |
|
1157 | 1144 | }; |
|
1158 | 1145 | meta = { |
|
1159 | 1146 | license = [ pkgs.lib.licenses.mit ]; |
|
1160 | 1147 | }; |
|
1161 | 1148 | }; |
|
1162 | 1149 | peppercorn = super.buildPythonPackage { |
|
1163 | 1150 | name = "peppercorn-0.5"; |
|
1164 | 1151 | buildInputs = with self; []; |
|
1165 | 1152 | doCheck = false; |
|
1166 | 1153 | propagatedBuildInputs = with self; []; |
|
1167 | 1154 | src = fetchurl { |
|
1168 | 1155 | url = "https://pypi.python.org/packages/45/ec/a62ec317d1324a01567c5221b420742f094f05ee48097e5157d32be3755c/peppercorn-0.5.tar.gz"; |
|
1169 | 1156 | md5 = "f08efbca5790019ab45d76b7244abd40"; |
|
1170 | 1157 | }; |
|
1171 | 1158 | meta = { |
|
1172 | 1159 | license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ]; |
|
1173 | 1160 | }; |
|
1174 | 1161 | }; |
|
1175 | 1162 | pexpect = super.buildPythonPackage { |
|
1176 |
name = "pexpect-4. |
|
|
1163 | name = "pexpect-4.3.0"; | |
|
1177 | 1164 | buildInputs = with self; []; |
|
1178 | 1165 | doCheck = false; |
|
1179 | 1166 | propagatedBuildInputs = with self; [ptyprocess]; |
|
1180 | 1167 | src = fetchurl { |
|
1181 | url = "https://pypi.python.org/packages/e8/13/d0b0599099d6cd23663043a2a0bb7c61e58c6ba359b2656e6fb000ef5b98/pexpect-4.2.1.tar.gz"; | |
|
1182 | md5 = "3694410001a99dff83f0b500a1ca1c95"; | |
|
1168 | url = "https://pypi.python.org/packages/f8/44/5466c30e49762bb92e442bbdf4472d6904608d211258eb3198a11f0309a4/pexpect-4.3.0.tar.gz"; | |
|
1169 | md5 = "047a486dcd26134b74f2e67046bb61a0"; | |
|
1183 | 1170 | }; |
|
1184 | 1171 | meta = { |
|
1185 | 1172 | license = [ pkgs.lib.licenses.isc { fullName = "ISC License (ISCL)"; } ]; |
|
1186 | 1173 | }; |
|
1187 | 1174 | }; |
|
1188 | 1175 | pickleshare = super.buildPythonPackage { |
|
1189 | 1176 | name = "pickleshare-0.7.4"; |
|
1190 | 1177 | buildInputs = with self; []; |
|
1191 | 1178 | doCheck = false; |
|
1192 | 1179 | propagatedBuildInputs = with self; [pathlib2]; |
|
1193 | 1180 | src = fetchurl { |
|
1194 | 1181 | url = "https://pypi.python.org/packages/69/fe/dd137d84daa0fd13a709e448138e310d9ea93070620c9db5454e234af525/pickleshare-0.7.4.tar.gz"; |
|
1195 | 1182 | md5 = "6a9e5dd8dfc023031f6b7b3f824cab12"; |
|
1196 | 1183 | }; |
|
1197 | 1184 | meta = { |
|
1198 | 1185 | license = [ pkgs.lib.licenses.mit ]; |
|
1199 | 1186 | }; |
|
1200 | 1187 | }; |
|
1201 | 1188 | plaster = super.buildPythonPackage { |
|
1202 | 1189 | name = "plaster-1.0"; |
|
1203 | 1190 | buildInputs = with self; []; |
|
1204 | 1191 | doCheck = false; |
|
1205 | 1192 | propagatedBuildInputs = with self; [setuptools]; |
|
1206 | 1193 | src = fetchurl { |
|
1207 | 1194 | url = "https://pypi.python.org/packages/37/e1/56d04382d718d32751017d32f351214384e529b794084eee20bb52405563/plaster-1.0.tar.gz"; |
|
1208 | 1195 | md5 = "80e6beb4760c16fea31754babcc0576e"; |
|
1209 | 1196 | }; |
|
1210 | 1197 | meta = { |
|
1211 | 1198 | license = [ pkgs.lib.licenses.mit ]; |
|
1212 | 1199 | }; |
|
1213 | 1200 | }; |
|
1214 | 1201 | plaster-pastedeploy = super.buildPythonPackage { |
|
1215 | 1202 | name = "plaster-pastedeploy-0.4.1"; |
|
1216 | 1203 | buildInputs = with self; []; |
|
1217 | 1204 | doCheck = false; |
|
1218 | 1205 | propagatedBuildInputs = with self; [PasteDeploy plaster]; |
|
1219 | 1206 | src = fetchurl { |
|
1220 | 1207 | url = "https://pypi.python.org/packages/9d/6e/f8be01ed41c94e6c54ac97cf2eb142a702aae0c8cce31c846f785e525b40/plaster_pastedeploy-0.4.1.tar.gz"; |
|
1221 | 1208 | md5 = "f48d5344b922e56c4978eebf1cd2e0d3"; |
|
1222 | 1209 | }; |
|
1223 | 1210 | meta = { |
|
1224 | 1211 | license = [ pkgs.lib.licenses.mit ]; |
|
1225 | 1212 | }; |
|
1226 | 1213 | }; |
|
1227 | 1214 | prompt-toolkit = super.buildPythonPackage { |
|
1228 | 1215 | name = "prompt-toolkit-1.0.15"; |
|
1229 | 1216 | buildInputs = with self; []; |
|
1230 | 1217 | doCheck = false; |
|
1231 | 1218 | propagatedBuildInputs = with self; [six wcwidth]; |
|
1232 | 1219 | src = fetchurl { |
|
1233 | 1220 | url = "https://pypi.python.org/packages/8a/ad/cf6b128866e78ad6d7f1dc5b7f99885fb813393d9860778b2984582e81b5/prompt_toolkit-1.0.15.tar.gz"; |
|
1234 | 1221 | md5 = "8fe70295006dbc8afedd43e5eba99032"; |
|
1235 | 1222 | }; |
|
1236 | 1223 | meta = { |
|
1237 | 1224 | license = [ pkgs.lib.licenses.bsdOriginal ]; |
|
1238 | 1225 | }; |
|
1239 | 1226 | }; |
|
1240 | 1227 | psutil = super.buildPythonPackage { |
|
1241 | 1228 | name = "psutil-5.4.0"; |
|
1242 | 1229 | buildInputs = with self; []; |
|
1243 | 1230 | doCheck = false; |
|
1244 | 1231 | propagatedBuildInputs = with self; []; |
|
1245 | 1232 | src = fetchurl { |
|
1246 | 1233 | url = "https://pypi.python.org/packages/8d/96/1fc6468be91521192861966c40bd73fdf8b065eae6d82dd0f870b9825a65/psutil-5.4.0.tar.gz"; |
|
1247 | 1234 | md5 = "01af6219b1e8fcfd53603023967713bf"; |
|
1248 | 1235 | }; |
|
1249 | 1236 | meta = { |
|
1250 | 1237 | license = [ pkgs.lib.licenses.bsdOriginal ]; |
|
1251 | 1238 | }; |
|
1252 | 1239 | }; |
|
1253 | 1240 | psycopg2 = super.buildPythonPackage { |
|
1254 | 1241 | name = "psycopg2-2.7.3.2"; |
|
1255 | 1242 | buildInputs = with self; []; |
|
1256 | 1243 | doCheck = false; |
|
1257 | 1244 | propagatedBuildInputs = with self; []; |
|
1258 | 1245 | src = fetchurl { |
|
1259 | 1246 | url = "https://pypi.python.org/packages/dd/47/000b405d73ca22980684fd7bd3318690cc03cfa3b2ae1c5b7fff8050b28a/psycopg2-2.7.3.2.tar.gz"; |
|
1260 | 1247 | md5 = "8114e672d5f23fa5329874a4314fbd6f"; |
|
1261 | 1248 | }; |
|
1262 | 1249 | meta = { |
|
1263 | 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 | 1253 | ptyprocess = super.buildPythonPackage { |
|
1267 | 1254 | name = "ptyprocess-0.5.2"; |
|
1268 | 1255 | buildInputs = with self; []; |
|
1269 | 1256 | doCheck = false; |
|
1270 | 1257 | propagatedBuildInputs = with self; []; |
|
1271 | 1258 | src = fetchurl { |
|
1272 | 1259 | url = "https://pypi.python.org/packages/51/83/5d07dc35534640b06f9d9f1a1d2bc2513fb9cc7595a1b0e28ae5477056ce/ptyprocess-0.5.2.tar.gz"; |
|
1273 | 1260 | md5 = "d3b8febae1b8c53b054bd818d0bb8665"; |
|
1274 | 1261 | }; |
|
1275 | 1262 | meta = { |
|
1276 | 1263 | license = [ ]; |
|
1277 | 1264 | }; |
|
1278 | 1265 | }; |
|
1279 | 1266 | py = super.buildPythonPackage { |
|
1280 | 1267 | name = "py-1.4.34"; |
|
1281 | 1268 | buildInputs = with self; []; |
|
1282 | 1269 | doCheck = false; |
|
1283 | 1270 | propagatedBuildInputs = with self; []; |
|
1284 | 1271 | src = fetchurl { |
|
1285 | 1272 | url = "https://pypi.python.org/packages/68/35/58572278f1c097b403879c1e9369069633d1cbad5239b9057944bb764782/py-1.4.34.tar.gz"; |
|
1286 | 1273 | md5 = "d9c3d8f734b0819ff48e355d77bf1730"; |
|
1287 | 1274 | }; |
|
1288 | 1275 | meta = { |
|
1289 | 1276 | license = [ pkgs.lib.licenses.mit ]; |
|
1290 | 1277 | }; |
|
1291 | 1278 | }; |
|
1292 | 1279 | py-bcrypt = super.buildPythonPackage { |
|
1293 | 1280 | name = "py-bcrypt-0.4"; |
|
1294 | 1281 | buildInputs = with self; []; |
|
1295 | 1282 | doCheck = false; |
|
1296 | 1283 | propagatedBuildInputs = with self; []; |
|
1297 | 1284 | src = fetchurl { |
|
1298 | 1285 | url = "https://pypi.python.org/packages/68/b1/1c3068c5c4d2e35c48b38dcc865301ebfdf45f54507086ac65ced1fd3b3d/py-bcrypt-0.4.tar.gz"; |
|
1299 | 1286 | md5 = "dd8b367d6b716a2ea2e72392525f4e36"; |
|
1300 | 1287 | }; |
|
1301 | 1288 | meta = { |
|
1302 | 1289 | license = [ pkgs.lib.licenses.bsdOriginal ]; |
|
1303 | 1290 | }; |
|
1304 | 1291 | }; |
|
1305 | 1292 | py-gfm = super.buildPythonPackage { |
|
1306 | 1293 | name = "py-gfm-0.1.3"; |
|
1307 | 1294 | buildInputs = with self; []; |
|
1308 | 1295 | doCheck = false; |
|
1309 | 1296 | propagatedBuildInputs = with self; [setuptools Markdown]; |
|
1310 | 1297 | src = fetchurl { |
|
1311 | 1298 | url = "https://pypi.python.org/packages/12/e4/6b3d8678da04f97d7490d8264d8de51c2dc9fb91209ccee9c515c95e14c5/py-gfm-0.1.3.tar.gz"; |
|
1312 | 1299 | md5 = "e588d9e69640a241b97e2c59c22527a6"; |
|
1313 | 1300 | }; |
|
1314 | 1301 | meta = { |
|
1315 | 1302 | license = [ pkgs.lib.licenses.bsdOriginal ]; |
|
1316 | 1303 | }; |
|
1317 | 1304 | }; |
|
1318 | 1305 | pycrypto = super.buildPythonPackage { |
|
1319 | 1306 | name = "pycrypto-2.6.1"; |
|
1320 | 1307 | buildInputs = with self; []; |
|
1321 | 1308 | doCheck = false; |
|
1322 | 1309 | propagatedBuildInputs = with self; []; |
|
1323 | 1310 | src = fetchurl { |
|
1324 | 1311 | url = "https://pypi.python.org/packages/60/db/645aa9af249f059cc3a368b118de33889219e0362141e75d4eaf6f80f163/pycrypto-2.6.1.tar.gz"; |
|
1325 | 1312 | md5 = "55a61a054aa66812daf5161a0d5d7eda"; |
|
1326 | 1313 | }; |
|
1327 | 1314 | meta = { |
|
1328 | 1315 | license = [ pkgs.lib.licenses.publicDomain ]; |
|
1329 | 1316 | }; |
|
1330 | 1317 | }; |
|
1331 | 1318 | pycurl = super.buildPythonPackage { |
|
1332 | 1319 | name = "pycurl-7.19.5"; |
|
1333 | 1320 | buildInputs = with self; []; |
|
1334 | 1321 | doCheck = false; |
|
1335 | 1322 | propagatedBuildInputs = with self; []; |
|
1336 | 1323 | src = fetchurl { |
|
1337 | 1324 | url = "https://pypi.python.org/packages/6c/48/13bad289ef6f4869b1d8fc11ae54de8cfb3cc4a2eb9f7419c506f763be46/pycurl-7.19.5.tar.gz"; |
|
1338 | 1325 | md5 = "47b4eac84118e2606658122104e62072"; |
|
1339 | 1326 | }; |
|
1340 | 1327 | meta = { |
|
1341 | 1328 | license = [ pkgs.lib.licenses.mit { fullName = "LGPL/MIT"; } { fullName = "GNU Library or Lesser General Public License (LGPL)"; } ]; |
|
1342 | 1329 | }; |
|
1343 | 1330 | }; |
|
1344 | 1331 | pyflakes = super.buildPythonPackage { |
|
1345 | 1332 | name = "pyflakes-0.8.1"; |
|
1346 | 1333 | buildInputs = with self; []; |
|
1347 | 1334 | doCheck = false; |
|
1348 | 1335 | propagatedBuildInputs = with self; []; |
|
1349 | 1336 | src = fetchurl { |
|
1350 | 1337 | url = "https://pypi.python.org/packages/75/22/a90ec0252f4f87f3ffb6336504de71fe16a49d69c4538dae2f12b9360a38/pyflakes-0.8.1.tar.gz"; |
|
1351 | 1338 | md5 = "905fe91ad14b912807e8fdc2ac2e2c23"; |
|
1352 | 1339 | }; |
|
1353 | 1340 | meta = { |
|
1354 | 1341 | license = [ pkgs.lib.licenses.mit ]; |
|
1355 | 1342 | }; |
|
1356 | 1343 | }; |
|
1357 | 1344 | pygments-markdown-lexer = super.buildPythonPackage { |
|
1358 | 1345 | name = "pygments-markdown-lexer-0.1.0.dev39"; |
|
1359 | 1346 | buildInputs = with self; []; |
|
1360 | 1347 | doCheck = false; |
|
1361 | 1348 | propagatedBuildInputs = with self; [Pygments]; |
|
1362 | 1349 | src = fetchurl { |
|
1363 | 1350 | url = "https://pypi.python.org/packages/c3/12/674cdee66635d638cedb2c5d9c85ce507b7b2f91bdba29e482f1b1160ff6/pygments-markdown-lexer-0.1.0.dev39.zip"; |
|
1364 | 1351 | md5 = "6360fe0f6d1f896e35b7a0142ce6459c"; |
|
1365 | 1352 | }; |
|
1366 | 1353 | meta = { |
|
1367 | 1354 | license = [ pkgs.lib.licenses.asl20 ]; |
|
1368 | 1355 | }; |
|
1369 | 1356 | }; |
|
1370 | 1357 | pyparsing = super.buildPythonPackage { |
|
1371 | 1358 | name = "pyparsing-1.5.7"; |
|
1372 | 1359 | buildInputs = with self; []; |
|
1373 | 1360 | doCheck = false; |
|
1374 | 1361 | propagatedBuildInputs = with self; []; |
|
1375 | 1362 | src = fetchurl { |
|
1376 | 1363 | url = "https://pypi.python.org/packages/2e/26/e8fb5b4256a5f5036be7ce115ef8db8d06bc537becfbdc46c6af008314ee/pyparsing-1.5.7.zip"; |
|
1377 | 1364 | md5 = "b86854857a368d6ccb4d5b6e76d0637f"; |
|
1378 | 1365 | }; |
|
1379 | 1366 | meta = { |
|
1380 | 1367 | license = [ pkgs.lib.licenses.mit ]; |
|
1381 | 1368 | }; |
|
1382 | 1369 | }; |
|
1383 | 1370 | pyramid = super.buildPythonPackage { |
|
1384 | 1371 | name = "pyramid-1.9.1"; |
|
1385 | 1372 | buildInputs = with self; []; |
|
1386 | 1373 | doCheck = false; |
|
1387 | 1374 | propagatedBuildInputs = with self; [setuptools WebOb repoze.lru zope.interface zope.deprecation venusian translationstring PasteDeploy plaster plaster-pastedeploy hupper]; |
|
1388 | 1375 | src = fetchurl { |
|
1389 | 1376 | url = "https://pypi.python.org/packages/9a/57/73447be9e7d0512d601e3f0a1fb9d7d1efb941911f49efdfe036d2826507/pyramid-1.9.1.tar.gz"; |
|
1390 | 1377 | md5 = "0163e19c58c2d12976a3b6fdb57e052d"; |
|
1391 | 1378 | }; |
|
1392 | 1379 | meta = { |
|
1393 | 1380 | license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ]; |
|
1394 | 1381 | }; |
|
1395 | 1382 | }; |
|
1396 | 1383 | pyramid-beaker = super.buildPythonPackage { |
|
1397 | 1384 | name = "pyramid-beaker-0.8"; |
|
1398 | 1385 | buildInputs = with self; []; |
|
1399 | 1386 | doCheck = false; |
|
1400 | 1387 | propagatedBuildInputs = with self; [pyramid Beaker]; |
|
1401 | 1388 | src = fetchurl { |
|
1402 | 1389 | url = "https://pypi.python.org/packages/d9/6e/b85426e00fd3d57f4545f74e1c3828552d8700f13ededeef9233f7bca8be/pyramid_beaker-0.8.tar.gz"; |
|
1403 | 1390 | md5 = "22f14be31b06549f80890e2c63a93834"; |
|
1404 | 1391 | }; |
|
1405 | 1392 | meta = { |
|
1406 | 1393 | license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ]; |
|
1407 | 1394 | }; |
|
1408 | 1395 | }; |
|
1409 | 1396 | pyramid-debugtoolbar = super.buildPythonPackage { |
|
1410 | 1397 | name = "pyramid-debugtoolbar-4.3"; |
|
1411 | 1398 | buildInputs = with self; []; |
|
1412 | 1399 | doCheck = false; |
|
1413 | 1400 | propagatedBuildInputs = with self; [pyramid pyramid-mako repoze.lru Pygments ipaddress]; |
|
1414 | 1401 | src = fetchurl { |
|
1415 | 1402 | url = "https://pypi.python.org/packages/a4/40/f09d8800bfc3c09bdb6c95f37bb61c890dc62c19c4e7caa304da7aa77403/pyramid_debugtoolbar-4.3.tar.gz"; |
|
1416 | 1403 | md5 = "9c49029e9f0695130499ef6416ffaaf8"; |
|
1417 | 1404 | }; |
|
1418 | 1405 | meta = { |
|
1419 | 1406 | license = [ { fullName = "Repoze Public License"; } pkgs.lib.licenses.bsdOriginal ]; |
|
1420 | 1407 | }; |
|
1421 | 1408 | }; |
|
1422 | 1409 | pyramid-jinja2 = super.buildPythonPackage { |
|
1423 | 1410 | name = "pyramid-jinja2-2.7"; |
|
1424 | 1411 | buildInputs = with self; []; |
|
1425 | 1412 | doCheck = false; |
|
1426 | 1413 | propagatedBuildInputs = with self; [pyramid zope.deprecation Jinja2 MarkupSafe]; |
|
1427 | 1414 | src = fetchurl { |
|
1428 | 1415 | url = "https://pypi.python.org/packages/d8/80/d60a7233823de22ce77bd864a8a83736a1fe8b49884b08303a2e68b2c853/pyramid_jinja2-2.7.tar.gz"; |
|
1429 | 1416 | md5 = "c2f8b2cd7b73a6f1d9a311fcfaf4fb92"; |
|
1430 | 1417 | }; |
|
1431 | 1418 | meta = { |
|
1432 | 1419 | license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ]; |
|
1433 | 1420 | }; |
|
1434 | 1421 | }; |
|
1435 | 1422 | pyramid-mako = super.buildPythonPackage { |
|
1436 | 1423 | name = "pyramid-mako-1.0.2"; |
|
1437 | 1424 | buildInputs = with self; []; |
|
1438 | 1425 | doCheck = false; |
|
1439 | 1426 | propagatedBuildInputs = with self; [pyramid Mako]; |
|
1440 | 1427 | src = fetchurl { |
|
1441 | 1428 | url = "https://pypi.python.org/packages/f1/92/7e69bcf09676d286a71cb3bbb887b16595b96f9ba7adbdc239ffdd4b1eb9/pyramid_mako-1.0.2.tar.gz"; |
|
1442 | 1429 | md5 = "ee25343a97eb76bd90abdc2a774eb48a"; |
|
1443 | 1430 | }; |
|
1444 | 1431 | meta = { |
|
1445 | 1432 | license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ]; |
|
1446 | 1433 | }; |
|
1447 | 1434 | }; |
|
1448 | 1435 | pysqlite = super.buildPythonPackage { |
|
1449 | 1436 | name = "pysqlite-2.8.3"; |
|
1450 | 1437 | buildInputs = with self; []; |
|
1451 | 1438 | doCheck = false; |
|
1452 | 1439 | propagatedBuildInputs = with self; []; |
|
1453 | 1440 | src = fetchurl { |
|
1454 | 1441 | url = "https://pypi.python.org/packages/42/02/981b6703e3c83c5b25a829c6e77aad059f9481b0bbacb47e6e8ca12bd731/pysqlite-2.8.3.tar.gz"; |
|
1455 | 1442 | md5 = "033f17b8644577715aee55e8832ac9fc"; |
|
1456 | 1443 | }; |
|
1457 | 1444 | meta = { |
|
1458 | 1445 | license = [ { fullName = "zlib/libpng License"; } { fullName = "zlib/libpng license"; } ]; |
|
1459 | 1446 | }; |
|
1460 | 1447 | }; |
|
1461 | 1448 | pytest = super.buildPythonPackage { |
|
1462 | 1449 | name = "pytest-3.2.3"; |
|
1463 | 1450 | buildInputs = with self; []; |
|
1464 | 1451 | doCheck = false; |
|
1465 | 1452 | propagatedBuildInputs = with self; [py setuptools]; |
|
1466 | 1453 | src = fetchurl { |
|
1467 | 1454 | url = "https://pypi.python.org/packages/53/d0/208853c09be8377e6d4de7c0df875ef7ef37189373d76a74b65b44e50528/pytest-3.2.3.tar.gz"; |
|
1468 | 1455 | md5 = "698f8929e095a1c37876b5567943be79"; |
|
1469 | 1456 | }; |
|
1470 | 1457 | meta = { |
|
1471 | 1458 | license = [ pkgs.lib.licenses.mit ]; |
|
1472 | 1459 | }; |
|
1473 | 1460 | }; |
|
1474 | 1461 | pytest-catchlog = super.buildPythonPackage { |
|
1475 | 1462 | name = "pytest-catchlog-1.2.2"; |
|
1476 | 1463 | buildInputs = with self; []; |
|
1477 | 1464 | doCheck = false; |
|
1478 | 1465 | propagatedBuildInputs = with self; [py pytest]; |
|
1479 | 1466 | src = fetchurl { |
|
1480 | 1467 | url = "https://pypi.python.org/packages/f2/2b/2faccdb1a978fab9dd0bf31cca9f6847fbe9184a0bdcc3011ac41dd44191/pytest-catchlog-1.2.2.zip"; |
|
1481 | 1468 | md5 = "09d890c54c7456c818102b7ff8c182c8"; |
|
1482 | 1469 | }; |
|
1483 | 1470 | meta = { |
|
1484 | 1471 | license = [ pkgs.lib.licenses.mit ]; |
|
1485 | 1472 | }; |
|
1486 | 1473 | }; |
|
1487 | 1474 | pytest-cov = super.buildPythonPackage { |
|
1488 | 1475 | name = "pytest-cov-2.5.1"; |
|
1489 | 1476 | buildInputs = with self; []; |
|
1490 | 1477 | doCheck = false; |
|
1491 | 1478 | propagatedBuildInputs = with self; [pytest coverage]; |
|
1492 | 1479 | src = fetchurl { |
|
1493 | 1480 | url = "https://pypi.python.org/packages/24/b4/7290d65b2f3633db51393bdf8ae66309b37620bc3ec116c5e357e3e37238/pytest-cov-2.5.1.tar.gz"; |
|
1494 | 1481 | md5 = "5acf38d4909e19819eb5c1754fbfc0ac"; |
|
1495 | 1482 | }; |
|
1496 | 1483 | meta = { |
|
1497 | 1484 | license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.mit ]; |
|
1498 | 1485 | }; |
|
1499 | 1486 | }; |
|
1500 | 1487 | pytest-profiling = super.buildPythonPackage { |
|
1501 | 1488 | name = "pytest-profiling-1.2.11"; |
|
1502 | 1489 | buildInputs = with self; []; |
|
1503 | 1490 | doCheck = false; |
|
1504 | 1491 | propagatedBuildInputs = with self; [six pytest gprof2dot]; |
|
1505 | 1492 | src = fetchurl { |
|
1506 | 1493 | url = "https://pypi.python.org/packages/c0/4a/b4aa786e93c07a86f1f87c581a36bf355a9e06a9da7e00dbd05047626bd2/pytest-profiling-1.2.11.tar.gz"; |
|
1507 | 1494 | md5 = "9ef6b60248731be5d44477980408e8f7"; |
|
1508 | 1495 | }; |
|
1509 | 1496 | meta = { |
|
1510 | 1497 | license = [ pkgs.lib.licenses.mit ]; |
|
1511 | 1498 | }; |
|
1512 | 1499 | }; |
|
1513 | 1500 | pytest-runner = super.buildPythonPackage { |
|
1514 | 1501 | name = "pytest-runner-2.11.1"; |
|
1515 | 1502 | buildInputs = with self; []; |
|
1516 | 1503 | doCheck = false; |
|
1517 | 1504 | propagatedBuildInputs = with self; []; |
|
1518 | 1505 | src = fetchurl { |
|
1519 | 1506 | url = "https://pypi.python.org/packages/9e/4d/08889e5e27a9f5d6096b9ad257f4dea1faabb03c5ded8f665ead448f5d8a/pytest-runner-2.11.1.tar.gz"; |
|
1520 | 1507 | md5 = "bdb73eb18eca2727944a2dcf963c5a81"; |
|
1521 | 1508 | }; |
|
1522 | 1509 | meta = { |
|
1523 | 1510 | license = [ pkgs.lib.licenses.mit ]; |
|
1524 | 1511 | }; |
|
1525 | 1512 | }; |
|
1526 | 1513 | pytest-sugar = super.buildPythonPackage { |
|
1527 | 1514 | name = "pytest-sugar-0.9.0"; |
|
1528 | 1515 | buildInputs = with self; []; |
|
1529 | 1516 | doCheck = false; |
|
1530 | 1517 | propagatedBuildInputs = with self; [pytest termcolor]; |
|
1531 | 1518 | src = fetchurl { |
|
1532 | 1519 | url = "https://pypi.python.org/packages/49/d8/c5ff6cca3ce2ebd8b73eec89779bf6b4a7737456a70e8ea4d44c1ff90f71/pytest-sugar-0.9.0.tar.gz"; |
|
1533 | 1520 | md5 = "89fbff17277fa6a95a560a04b68cb9f9"; |
|
1534 | 1521 | }; |
|
1535 | 1522 | meta = { |
|
1536 | 1523 | license = [ pkgs.lib.licenses.bsdOriginal ]; |
|
1537 | 1524 | }; |
|
1538 | 1525 | }; |
|
1539 | 1526 | pytest-timeout = super.buildPythonPackage { |
|
1540 | 1527 | name = "pytest-timeout-1.2.0"; |
|
1541 | 1528 | buildInputs = with self; []; |
|
1542 | 1529 | doCheck = false; |
|
1543 | 1530 | propagatedBuildInputs = with self; [pytest]; |
|
1544 | 1531 | src = fetchurl { |
|
1545 | 1532 | url = "https://pypi.python.org/packages/cc/b7/b2a61365ea6b6d2e8881360ae7ed8dad0327ad2df89f2f0be4a02304deb2/pytest-timeout-1.2.0.tar.gz"; |
|
1546 | 1533 | md5 = "83607d91aa163562c7ee835da57d061d"; |
|
1547 | 1534 | }; |
|
1548 | 1535 | meta = { |
|
1549 | 1536 | license = [ pkgs.lib.licenses.mit { fullName = "DFSG approved"; } ]; |
|
1550 | 1537 | }; |
|
1551 | 1538 | }; |
|
1552 | 1539 | python-dateutil = super.buildPythonPackage { |
|
1553 | name = "python-dateutil-2.1"; | |
|
1540 | name = "python-dateutil-2.6.1"; | |
|
1554 | 1541 | buildInputs = with self; []; |
|
1555 | 1542 | doCheck = false; |
|
1556 | 1543 | propagatedBuildInputs = with self; [six]; |
|
1557 | 1544 | src = fetchurl { |
|
1558 |
url = "https://pypi.python.org/packages/65 |
|
|
1559 | md5 = "1534bb15cf311f07afaa3aacba1c028b"; | |
|
1545 | url = "https://pypi.python.org/packages/54/bb/f1db86504f7a49e1d9b9301531181b00a1c7325dc85a29160ee3eaa73a54/python-dateutil-2.6.1.tar.gz"; | |
|
1546 | md5 = "db38f6b4511cefd76014745bb0cc45a4"; | |
|
1560 | 1547 | }; |
|
1561 | 1548 | meta = { |
|
1562 | license = [ { fullName = "Simplified BSD"; } ]; | |
|
1549 | license = [ pkgs.lib.licenses.bsdOriginal { fullName = "Simplified BSD"; } ]; | |
|
1563 | 1550 | }; |
|
1564 | 1551 | }; |
|
1565 | 1552 | python-editor = super.buildPythonPackage { |
|
1566 | 1553 | name = "python-editor-1.0.3"; |
|
1567 | 1554 | buildInputs = with self; []; |
|
1568 | 1555 | doCheck = false; |
|
1569 | 1556 | propagatedBuildInputs = with self; []; |
|
1570 | 1557 | src = fetchurl { |
|
1571 | 1558 | url = "https://pypi.python.org/packages/65/1e/adf6e000ea5dc909aa420352d6ba37f16434c8a3c2fa030445411a1ed545/python-editor-1.0.3.tar.gz"; |
|
1572 | 1559 | md5 = "0aca5f2ef176ce68e98a5b7e31372835"; |
|
1573 | 1560 | }; |
|
1574 | 1561 | meta = { |
|
1575 | 1562 | license = [ pkgs.lib.licenses.asl20 { fullName = "Apache"; } ]; |
|
1576 | 1563 | }; |
|
1577 | 1564 | }; |
|
1578 | 1565 | python-ldap = super.buildPythonPackage { |
|
1579 | 1566 | name = "python-ldap-2.4.45"; |
|
1580 | 1567 | buildInputs = with self; []; |
|
1581 | 1568 | doCheck = false; |
|
1582 | 1569 | propagatedBuildInputs = with self; [setuptools]; |
|
1583 | 1570 | src = fetchurl { |
|
1584 | 1571 | url = "https://pypi.python.org/packages/ce/52/6b5372d0166820f4a4b0a88ed73dc7504219355049fc1d266d8ccdb7942e/python-ldap-2.4.45.tar.gz"; |
|
1585 | 1572 | md5 = "6108e189a44eea8bc7d1cc281c222978"; |
|
1586 | 1573 | }; |
|
1587 | 1574 | meta = { |
|
1588 | 1575 | license = [ pkgs.lib.licenses.psfl ]; |
|
1589 | 1576 | }; |
|
1590 | 1577 | }; |
|
1591 | 1578 | python-memcached = super.buildPythonPackage { |
|
1592 | 1579 | name = "python-memcached-1.58"; |
|
1593 | 1580 | buildInputs = with self; []; |
|
1594 | 1581 | doCheck = false; |
|
1595 | 1582 | propagatedBuildInputs = with self; [six]; |
|
1596 | 1583 | src = fetchurl { |
|
1597 | 1584 | url = "https://pypi.python.org/packages/f7/62/14b2448cfb04427366f24104c9da97cf8ea380d7258a3233f066a951a8d8/python-memcached-1.58.tar.gz"; |
|
1598 | 1585 | md5 = "23b258105013d14d899828d334e6b044"; |
|
1599 | 1586 | }; |
|
1600 | 1587 | meta = { |
|
1601 | 1588 | license = [ pkgs.lib.licenses.psfl ]; |
|
1602 | 1589 | }; |
|
1603 | 1590 | }; |
|
1604 | 1591 | python-pam = super.buildPythonPackage { |
|
1605 | 1592 | name = "python-pam-1.8.2"; |
|
1606 | 1593 | buildInputs = with self; []; |
|
1607 | 1594 | doCheck = false; |
|
1608 | 1595 | propagatedBuildInputs = with self; []; |
|
1609 | 1596 | src = fetchurl { |
|
1610 | 1597 | url = "https://pypi.python.org/packages/de/8c/f8f5d38b4f26893af267ea0b39023d4951705ab0413a39e0cf7cf4900505/python-pam-1.8.2.tar.gz"; |
|
1611 | 1598 | md5 = "db71b6b999246fb05d78ecfbe166629d"; |
|
1612 | 1599 | }; |
|
1613 | 1600 | meta = { |
|
1614 | 1601 | license = [ { fullName = "License :: OSI Approved :: MIT License"; } pkgs.lib.licenses.mit ]; |
|
1615 | 1602 | }; |
|
1616 | 1603 | }; |
|
1617 | 1604 | pytz = super.buildPythonPackage { |
|
1618 |
name = "pytz-201 |
|
|
1605 | name = "pytz-2017.3"; | |
|
1619 | 1606 | buildInputs = with self; []; |
|
1620 | 1607 | doCheck = false; |
|
1621 | 1608 | propagatedBuildInputs = with self; []; |
|
1622 | 1609 | src = fetchurl { |
|
1623 |
url = "https://pypi.python.org/packages/ |
|
|
1624 | md5 = "233f2a2b370d03f9b5911700cc9ebf3c"; | |
|
1610 | url = "https://pypi.python.org/packages/60/88/d3152c234da4b2a1f7a989f89609ea488225eaea015bc16fbde2b3fdfefa/pytz-2017.3.zip"; | |
|
1611 | md5 = "7006b56c0d68a162d9fe57d4249c3171"; | |
|
1625 | 1612 | }; |
|
1626 | 1613 | meta = { |
|
1627 | 1614 | license = [ pkgs.lib.licenses.mit ]; |
|
1628 | 1615 | }; |
|
1629 | 1616 | }; |
|
1630 | 1617 | pyzmq = super.buildPythonPackage { |
|
1631 | 1618 | name = "pyzmq-14.6.0"; |
|
1632 | 1619 | buildInputs = with self; []; |
|
1633 | 1620 | doCheck = false; |
|
1634 | 1621 | propagatedBuildInputs = with self; []; |
|
1635 | 1622 | src = fetchurl { |
|
1636 | 1623 | url = "https://pypi.python.org/packages/8a/3b/5463d5a9d712cd8bbdac335daece0d69f6a6792da4e3dd89956c0db4e4e6/pyzmq-14.6.0.tar.gz"; |
|
1637 | 1624 | md5 = "395b5de95a931afa5b14c9349a5b8024"; |
|
1638 | 1625 | }; |
|
1639 | 1626 | meta = { |
|
1640 | 1627 | license = [ pkgs.lib.licenses.bsdOriginal { fullName = "LGPL+BSD"; } { fullName = "GNU Library or Lesser General Public License (LGPL)"; } ]; |
|
1641 | 1628 | }; |
|
1642 | 1629 | }; |
|
1643 | 1630 | recaptcha-client = super.buildPythonPackage { |
|
1644 | 1631 | name = "recaptcha-client-1.0.6"; |
|
1645 | 1632 | buildInputs = with self; []; |
|
1646 | 1633 | doCheck = false; |
|
1647 | 1634 | propagatedBuildInputs = with self; []; |
|
1648 | 1635 | src = fetchurl { |
|
1649 | 1636 | url = "https://pypi.python.org/packages/0a/ea/5f2fbbfd894bdac1c68ef8d92019066cfcf9fbff5fe3d728d2b5c25c8db4/recaptcha-client-1.0.6.tar.gz"; |
|
1650 | 1637 | md5 = "74228180f7e1fb76c4d7089160b0d919"; |
|
1651 | 1638 | }; |
|
1652 | 1639 | meta = { |
|
1653 | 1640 | license = [ { fullName = "MIT/X11"; } ]; |
|
1654 | 1641 | }; |
|
1655 | 1642 | }; |
|
1656 | 1643 | redis = super.buildPythonPackage { |
|
1657 | 1644 | name = "redis-2.10.6"; |
|
1658 | 1645 | buildInputs = with self; []; |
|
1659 | 1646 | doCheck = false; |
|
1660 | 1647 | propagatedBuildInputs = with self; []; |
|
1661 | 1648 | src = fetchurl { |
|
1662 | 1649 | url = "https://pypi.python.org/packages/09/8d/6d34b75326bf96d4139a2ddd8e74b80840f800a0a79f9294399e212cb9a7/redis-2.10.6.tar.gz"; |
|
1663 | 1650 | md5 = "048348d8cfe0b5d0bba2f4d835005c3b"; |
|
1664 | 1651 | }; |
|
1665 | 1652 | meta = { |
|
1666 | 1653 | license = [ pkgs.lib.licenses.mit ]; |
|
1667 | 1654 | }; |
|
1668 | 1655 | }; |
|
1669 | 1656 | repoze.lru = super.buildPythonPackage { |
|
1670 | 1657 | name = "repoze.lru-0.7"; |
|
1671 | 1658 | buildInputs = with self; []; |
|
1672 | 1659 | doCheck = false; |
|
1673 | 1660 | propagatedBuildInputs = with self; []; |
|
1674 | 1661 | src = fetchurl { |
|
1675 | 1662 | url = "https://pypi.python.org/packages/12/bc/595a77c4b5e204847fdf19268314ef59c85193a9dc9f83630fc459c0fee5/repoze.lru-0.7.tar.gz"; |
|
1676 | 1663 | md5 = "c08cc030387e0b1fc53c5c7d964b35e2"; |
|
1677 | 1664 | }; |
|
1678 | 1665 | meta = { |
|
1679 | 1666 | license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ]; |
|
1680 | 1667 | }; |
|
1681 | 1668 | }; |
|
1682 | 1669 | requests = super.buildPythonPackage { |
|
1683 | 1670 | name = "requests-2.9.1"; |
|
1684 | 1671 | buildInputs = with self; []; |
|
1685 | 1672 | doCheck = false; |
|
1686 | 1673 | propagatedBuildInputs = with self; []; |
|
1687 | 1674 | src = fetchurl { |
|
1688 | 1675 | url = "https://pypi.python.org/packages/f9/6d/07c44fb1ebe04d069459a189e7dab9e4abfe9432adcd4477367c25332748/requests-2.9.1.tar.gz"; |
|
1689 | 1676 | md5 = "0b7f480d19012ec52bab78292efd976d"; |
|
1690 | 1677 | }; |
|
1691 | 1678 | meta = { |
|
1692 | 1679 | license = [ pkgs.lib.licenses.asl20 ]; |
|
1693 | 1680 | }; |
|
1694 | 1681 | }; |
|
1695 | 1682 | rhodecode-enterprise-ce = super.buildPythonPackage { |
|
1696 | 1683 | name = "rhodecode-enterprise-ce-4.11.0"; |
|
1697 | 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 | 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 | 1687 | src = ./.; |
|
1701 | 1688 | meta = { |
|
1702 | 1689 | license = [ { fullName = "Affero GNU General Public License v3 or later (AGPLv3+)"; } { fullName = "AGPLv3, and Commercial License"; } ]; |
|
1703 | 1690 | }; |
|
1704 | 1691 | }; |
|
1705 | 1692 | rhodecode-tools = super.buildPythonPackage { |
|
1706 | 1693 | name = "rhodecode-tools-0.14.0"; |
|
1707 | 1694 | buildInputs = with self; []; |
|
1708 | 1695 | doCheck = false; |
|
1709 | 1696 | propagatedBuildInputs = with self; [click future six Mako MarkupSafe requests elasticsearch elasticsearch-dsl urllib3 Whoosh]; |
|
1710 | 1697 | src = fetchurl { |
|
1711 | 1698 | url = "https://code.rhodecode.com/rhodecode-tools-ce/archive/v0.14.0.tar.gz?md5=15de9be3d185d832c4af2156fefc8eeb"; |
|
1712 | 1699 | md5 = "15de9be3d185d832c4af2156fefc8eeb"; |
|
1713 | 1700 | }; |
|
1714 | 1701 | meta = { |
|
1715 | 1702 | license = [ { fullName = "AGPLv3 and Proprietary"; } ]; |
|
1716 | 1703 | }; |
|
1717 | 1704 | }; |
|
1718 | 1705 | scandir = super.buildPythonPackage { |
|
1719 | 1706 | name = "scandir-1.6"; |
|
1720 | 1707 | buildInputs = with self; []; |
|
1721 | 1708 | doCheck = false; |
|
1722 | 1709 | propagatedBuildInputs = with self; []; |
|
1723 | 1710 | src = fetchurl { |
|
1724 | 1711 | url = "https://pypi.python.org/packages/77/3f/916f524f50ee65e3f465a280d2851bd63685250fddb3020c212b3977664d/scandir-1.6.tar.gz"; |
|
1725 | 1712 | md5 = "0180ddb97c96cbb2d4f25d2ae11c64ac"; |
|
1726 | 1713 | }; |
|
1727 | 1714 | meta = { |
|
1728 | 1715 | license = [ pkgs.lib.licenses.bsdOriginal { fullName = "New BSD License"; } ]; |
|
1729 | 1716 | }; |
|
1730 | 1717 | }; |
|
1731 | 1718 | setproctitle = super.buildPythonPackage { |
|
1732 | 1719 | name = "setproctitle-1.1.10"; |
|
1733 | 1720 | buildInputs = with self; []; |
|
1734 | 1721 | doCheck = false; |
|
1735 | 1722 | propagatedBuildInputs = with self; []; |
|
1736 | 1723 | src = fetchurl { |
|
1737 | 1724 | url = "https://pypi.python.org/packages/5a/0d/dc0d2234aacba6cf1a729964383e3452c52096dc695581248b548786f2b3/setproctitle-1.1.10.tar.gz"; |
|
1738 | 1725 | md5 = "2dcdd1b761700a5a13252fea3dfd1977"; |
|
1739 | 1726 | }; |
|
1740 | 1727 | meta = { |
|
1741 | 1728 | license = [ pkgs.lib.licenses.bsdOriginal ]; |
|
1742 | 1729 | }; |
|
1743 | 1730 | }; |
|
1744 | 1731 | setuptools = super.buildPythonPackage { |
|
1745 | 1732 | name = "setuptools-30.1.0"; |
|
1746 | 1733 | buildInputs = with self; []; |
|
1747 | 1734 | doCheck = false; |
|
1748 | 1735 | propagatedBuildInputs = with self; []; |
|
1749 | 1736 | src = fetchurl { |
|
1750 | 1737 | url = "https://pypi.python.org/packages/1e/43/002c8616db9a3e7be23c2556e39b90a32bb40ba0dc652de1999d5334d372/setuptools-30.1.0.tar.gz"; |
|
1751 | 1738 | md5 = "cac497f42e5096ac8df29e38d3f81c3e"; |
|
1752 | 1739 | }; |
|
1753 | 1740 | meta = { |
|
1754 | 1741 | license = [ pkgs.lib.licenses.mit ]; |
|
1755 | 1742 | }; |
|
1756 | 1743 | }; |
|
1757 | 1744 | setuptools-scm = super.buildPythonPackage { |
|
1758 |
name = "setuptools-scm-1.15. |
|
|
1745 | name = "setuptools-scm-1.15.6"; | |
|
1759 | 1746 | buildInputs = with self; []; |
|
1760 | 1747 | doCheck = false; |
|
1761 | 1748 | propagatedBuildInputs = with self; []; |
|
1762 | 1749 | src = fetchurl { |
|
1763 |
url = "https://pypi.python.org/packages/8 |
|
|
1764 | md5 = "b6916c78ed6253d6602444fad4279c5b"; | |
|
1750 | url = "https://pypi.python.org/packages/03/6d/aafdd01edd227ee879b691455bf19895091872af7e48192bea1758c82032/setuptools_scm-1.15.6.tar.gz"; | |
|
1751 | md5 = "f17493d53f0d842bb0152f214775640b"; | |
|
1765 | 1752 | }; |
|
1766 | 1753 | meta = { |
|
1767 | 1754 | license = [ pkgs.lib.licenses.mit ]; |
|
1768 | 1755 | }; |
|
1769 | 1756 | }; |
|
1770 | 1757 | simplegeneric = super.buildPythonPackage { |
|
1771 | 1758 | name = "simplegeneric-0.8.1"; |
|
1772 | 1759 | buildInputs = with self; []; |
|
1773 | 1760 | doCheck = false; |
|
1774 | 1761 | propagatedBuildInputs = with self; []; |
|
1775 | 1762 | src = fetchurl { |
|
1776 | 1763 | url = "https://pypi.python.org/packages/3d/57/4d9c9e3ae9a255cd4e1106bb57e24056d3d0709fc01b2e3e345898e49d5b/simplegeneric-0.8.1.zip"; |
|
1777 | 1764 | md5 = "f9c1fab00fd981be588fc32759f474e3"; |
|
1778 | 1765 | }; |
|
1779 | 1766 | meta = { |
|
1780 | 1767 | license = [ pkgs.lib.licenses.zpt21 ]; |
|
1781 | 1768 | }; |
|
1782 | 1769 | }; |
|
1783 | 1770 | simplejson = super.buildPythonPackage { |
|
1784 | 1771 | name = "simplejson-3.11.1"; |
|
1785 | 1772 | buildInputs = with self; []; |
|
1786 | 1773 | doCheck = false; |
|
1787 | 1774 | propagatedBuildInputs = with self; []; |
|
1788 | 1775 | src = fetchurl { |
|
1789 | 1776 | url = "https://pypi.python.org/packages/08/48/c97b668d6da7d7bebe7ea1817a6f76394b0ec959cb04214ca833c34359df/simplejson-3.11.1.tar.gz"; |
|
1790 | 1777 | md5 = "6e2f1bd5fb0a926facf5d89d217a7183"; |
|
1791 | 1778 | }; |
|
1792 | 1779 | meta = { |
|
1793 | 1780 | license = [ { fullName = "Academic Free License (AFL)"; } pkgs.lib.licenses.mit ]; |
|
1794 | 1781 | }; |
|
1795 | 1782 | }; |
|
1796 | 1783 | six = super.buildPythonPackage { |
|
1797 | 1784 | name = "six-1.11.0"; |
|
1798 | 1785 | buildInputs = with self; []; |
|
1799 | 1786 | doCheck = false; |
|
1800 | 1787 | propagatedBuildInputs = with self; []; |
|
1801 | 1788 | src = fetchurl { |
|
1802 | 1789 | url = "https://pypi.python.org/packages/16/d8/bc6316cf98419719bd59c91742194c111b6f2e85abac88e496adefaf7afe/six-1.11.0.tar.gz"; |
|
1803 | 1790 | md5 = "d12789f9baf7e9fb2524c0c64f1773f8"; |
|
1804 | 1791 | }; |
|
1805 | 1792 | meta = { |
|
1806 | 1793 | license = [ pkgs.lib.licenses.mit ]; |
|
1807 | 1794 | }; |
|
1808 | 1795 | }; |
|
1809 | 1796 | sshpubkeys = super.buildPythonPackage { |
|
1810 | 1797 | name = "sshpubkeys-2.2.0"; |
|
1811 | 1798 | buildInputs = with self; []; |
|
1812 | 1799 | doCheck = false; |
|
1813 | 1800 | propagatedBuildInputs = with self; [pycrypto ecdsa]; |
|
1814 | 1801 | src = fetchurl { |
|
1815 | 1802 | url = "https://pypi.python.org/packages/27/da/337fabeb3dca6b62039a93ceaa636f25065e0ae92b575b1235342076cf0a/sshpubkeys-2.2.0.tar.gz"; |
|
1816 | 1803 | md5 = "458e45f6b92b1afa84f0ffe1f1c90935"; |
|
1817 | 1804 | }; |
|
1818 | 1805 | meta = { |
|
1819 | 1806 | license = [ pkgs.lib.licenses.bsdOriginal ]; |
|
1820 | 1807 | }; |
|
1821 | 1808 | }; |
|
1822 | 1809 | subprocess32 = super.buildPythonPackage { |
|
1823 | 1810 | name = "subprocess32-3.2.7"; |
|
1824 | 1811 | buildInputs = with self; []; |
|
1825 | 1812 | doCheck = false; |
|
1826 | 1813 | propagatedBuildInputs = with self; []; |
|
1827 | 1814 | src = fetchurl { |
|
1828 | 1815 | url = "https://pypi.python.org/packages/b8/2f/49e53b0d0e94611a2dc624a1ad24d41b6d94d0f1b0a078443407ea2214c2/subprocess32-3.2.7.tar.gz"; |
|
1829 | 1816 | md5 = "824c801e479d3e916879aae3e9c15e16"; |
|
1830 | 1817 | }; |
|
1831 | 1818 | meta = { |
|
1832 | 1819 | license = [ pkgs.lib.licenses.psfl ]; |
|
1833 | 1820 | }; |
|
1834 | 1821 | }; |
|
1835 | 1822 | termcolor = super.buildPythonPackage { |
|
1836 | 1823 | name = "termcolor-1.1.0"; |
|
1837 | 1824 | buildInputs = with self; []; |
|
1838 | 1825 | doCheck = false; |
|
1839 | 1826 | propagatedBuildInputs = with self; []; |
|
1840 | 1827 | src = fetchurl { |
|
1841 | 1828 | url = "https://pypi.python.org/packages/8a/48/a76be51647d0eb9f10e2a4511bf3ffb8cc1e6b14e9e4fab46173aa79f981/termcolor-1.1.0.tar.gz"; |
|
1842 | 1829 | md5 = "043e89644f8909d462fbbfa511c768df"; |
|
1843 | 1830 | }; |
|
1844 | 1831 | meta = { |
|
1845 | 1832 | license = [ pkgs.lib.licenses.mit ]; |
|
1846 | 1833 | }; |
|
1847 | 1834 | }; |
|
1848 | 1835 | testpath = super.buildPythonPackage { |
|
1849 | 1836 | name = "testpath-0.3.1"; |
|
1850 | 1837 | buildInputs = with self; []; |
|
1851 | 1838 | doCheck = false; |
|
1852 | 1839 | propagatedBuildInputs = with self; []; |
|
1853 | 1840 | src = fetchurl { |
|
1854 | 1841 | url = "https://pypi.python.org/packages/f4/8b/b71e9ee10e5f751e9d959bc750ab122ba04187f5aa52aabdc4e63b0e31a7/testpath-0.3.1.tar.gz"; |
|
1855 | 1842 | md5 = "2cd5ed5522fda781bb497c9d80ae2fc9"; |
|
1856 | 1843 | }; |
|
1857 | 1844 | meta = { |
|
1858 | 1845 | license = [ pkgs.lib.licenses.mit ]; |
|
1859 | 1846 | }; |
|
1860 | 1847 | }; |
|
1861 | 1848 | traitlets = super.buildPythonPackage { |
|
1862 | 1849 | name = "traitlets-4.3.2"; |
|
1863 | 1850 | buildInputs = with self; []; |
|
1864 | 1851 | doCheck = false; |
|
1865 | 1852 | propagatedBuildInputs = with self; [ipython-genutils six decorator enum34]; |
|
1866 | 1853 | src = fetchurl { |
|
1867 | 1854 | url = "https://pypi.python.org/packages/a5/98/7f5ef2fe9e9e071813aaf9cb91d1a732e0a68b6c44a32b38cb8e14c3f069/traitlets-4.3.2.tar.gz"; |
|
1868 | 1855 | md5 = "3068663f2f38fd939a9eb3a500ccc154"; |
|
1869 | 1856 | }; |
|
1870 | 1857 | meta = { |
|
1871 | 1858 | license = [ pkgs.lib.licenses.bsdOriginal ]; |
|
1872 | 1859 | }; |
|
1873 | 1860 | }; |
|
1874 | 1861 | transifex-client = super.buildPythonPackage { |
|
1875 | 1862 | name = "transifex-client-0.12.5"; |
|
1876 | 1863 | buildInputs = with self; []; |
|
1877 | 1864 | doCheck = false; |
|
1878 | 1865 | propagatedBuildInputs = with self; [urllib3 six]; |
|
1879 | 1866 | src = fetchurl { |
|
1880 | 1867 | url = "https://pypi.python.org/packages/7b/86/60f31a0c9b8d0b1266ce15b6c80b55f88522140c8acfc395d5aec5e23475/transifex-client-0.12.5.tar.gz"; |
|
1881 | 1868 | md5 = "e6e278117b23f60702c06e203b7e51ae"; |
|
1882 | 1869 | }; |
|
1883 | 1870 | meta = { |
|
1884 | 1871 | license = [ pkgs.lib.licenses.gpl2 ]; |
|
1885 | 1872 | }; |
|
1886 | 1873 | }; |
|
1887 | 1874 | translationstring = super.buildPythonPackage { |
|
1888 | 1875 | name = "translationstring-1.3"; |
|
1889 | 1876 | buildInputs = with self; []; |
|
1890 | 1877 | doCheck = false; |
|
1891 | 1878 | propagatedBuildInputs = with self; []; |
|
1892 | 1879 | src = fetchurl { |
|
1893 | 1880 | url = "https://pypi.python.org/packages/5e/eb/bee578cc150b44c653b63f5ebe258b5d0d812ddac12497e5f80fcad5d0b4/translationstring-1.3.tar.gz"; |
|
1894 | 1881 | md5 = "a4b62e0f3c189c783a1685b3027f7c90"; |
|
1895 | 1882 | }; |
|
1896 | 1883 | meta = { |
|
1897 | 1884 | license = [ { fullName = "BSD-like (http://repoze.org/license.html)"; } ]; |
|
1898 | 1885 | }; |
|
1899 | 1886 | }; |
|
1900 | 1887 | trollius = super.buildPythonPackage { |
|
1901 | 1888 | name = "trollius-1.0.4"; |
|
1902 | 1889 | buildInputs = with self; []; |
|
1903 | 1890 | doCheck = false; |
|
1904 | 1891 | propagatedBuildInputs = with self; [futures]; |
|
1905 | 1892 | src = fetchurl { |
|
1906 | 1893 | url = "https://pypi.python.org/packages/aa/e6/4141db437f55e6ee7a3fb69663239e3fde7841a811b4bef293145ad6c836/trollius-1.0.4.tar.gz"; |
|
1907 | 1894 | md5 = "3631a464d49d0cbfd30ab2918ef2b783"; |
|
1908 | 1895 | }; |
|
1909 | 1896 | meta = { |
|
1910 | 1897 | license = [ pkgs.lib.licenses.asl20 ]; |
|
1911 | 1898 | }; |
|
1912 | 1899 | }; |
|
1913 | 1900 | uWSGI = super.buildPythonPackage { |
|
1914 | 1901 | name = "uWSGI-2.0.15"; |
|
1915 | 1902 | buildInputs = with self; []; |
|
1916 | 1903 | doCheck = false; |
|
1917 | 1904 | propagatedBuildInputs = with self; []; |
|
1918 | 1905 | src = fetchurl { |
|
1919 | 1906 | url = "https://pypi.python.org/packages/bb/0a/45e5aa80dc135889594bb371c082d20fb7ee7303b174874c996888cc8511/uwsgi-2.0.15.tar.gz"; |
|
1920 | 1907 | md5 = "fc50bd9e83b7602fa474b032167010a7"; |
|
1921 | 1908 | }; |
|
1922 | 1909 | meta = { |
|
1923 | 1910 | license = [ pkgs.lib.licenses.gpl2 ]; |
|
1924 | 1911 | }; |
|
1925 | 1912 | }; |
|
1926 | 1913 | urllib3 = super.buildPythonPackage { |
|
1927 | 1914 | name = "urllib3-1.16"; |
|
1928 | 1915 | buildInputs = with self; []; |
|
1929 | 1916 | doCheck = false; |
|
1930 | 1917 | propagatedBuildInputs = with self; []; |
|
1931 | 1918 | src = fetchurl { |
|
1932 | 1919 | url = "https://pypi.python.org/packages/3b/f0/e763169124e3f5db0926bc3dbfcd580a105f9ca44cf5d8e6c7a803c9f6b5/urllib3-1.16.tar.gz"; |
|
1933 | 1920 | md5 = "fcaab1c5385c57deeb7053d3d7d81d59"; |
|
1934 | 1921 | }; |
|
1935 | 1922 | meta = { |
|
1936 | 1923 | license = [ pkgs.lib.licenses.mit ]; |
|
1937 | 1924 | }; |
|
1938 | 1925 | }; |
|
1939 | 1926 | venusian = super.buildPythonPackage { |
|
1940 | 1927 | name = "venusian-1.1.0"; |
|
1941 | 1928 | buildInputs = with self; []; |
|
1942 | 1929 | doCheck = false; |
|
1943 | 1930 | propagatedBuildInputs = with self; []; |
|
1944 | 1931 | src = fetchurl { |
|
1945 | 1932 | url = "https://pypi.python.org/packages/38/24/b4b470ab9e0a2e2e9b9030c7735828c8934b4c6b45befd1bb713ec2aeb2d/venusian-1.1.0.tar.gz"; |
|
1946 | 1933 | md5 = "56bc5e6756e4bda37bcdb94f74a72b8f"; |
|
1947 | 1934 | }; |
|
1948 | 1935 | meta = { |
|
1949 | 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 | 1952 | waitress = super.buildPythonPackage { |
|
1953 | 1953 | name = "waitress-1.1.0"; |
|
1954 | 1954 | buildInputs = with self; []; |
|
1955 | 1955 | doCheck = false; |
|
1956 | 1956 | propagatedBuildInputs = with self; []; |
|
1957 | 1957 | src = fetchurl { |
|
1958 | 1958 | url = "https://pypi.python.org/packages/3c/68/1c10dd5c556872ceebe88483b0436140048d39de83a84a06a8baa8136f4f/waitress-1.1.0.tar.gz"; |
|
1959 | 1959 | md5 = "0f1eb7fdfdbf2e6d18decbda1733045c"; |
|
1960 | 1960 | }; |
|
1961 | 1961 | meta = { |
|
1962 | 1962 | license = [ pkgs.lib.licenses.zpt21 ]; |
|
1963 | 1963 | }; |
|
1964 | 1964 | }; |
|
1965 | 1965 | wcwidth = super.buildPythonPackage { |
|
1966 | 1966 | name = "wcwidth-0.1.7"; |
|
1967 | 1967 | buildInputs = with self; []; |
|
1968 | 1968 | doCheck = false; |
|
1969 | 1969 | propagatedBuildInputs = with self; []; |
|
1970 | 1970 | src = fetchurl { |
|
1971 | 1971 | url = "https://pypi.python.org/packages/55/11/e4a2bb08bb450fdbd42cc709dd40de4ed2c472cf0ccb9e64af22279c5495/wcwidth-0.1.7.tar.gz"; |
|
1972 | 1972 | md5 = "b3b6a0a08f0c8a34d1de8cf44150a4ad"; |
|
1973 | 1973 | }; |
|
1974 | 1974 | meta = { |
|
1975 | 1975 | license = [ pkgs.lib.licenses.mit ]; |
|
1976 | 1976 | }; |
|
1977 | 1977 | }; |
|
1978 | 1978 | ws4py = super.buildPythonPackage { |
|
1979 | 1979 | name = "ws4py-0.4.2"; |
|
1980 | 1980 | buildInputs = with self; []; |
|
1981 | 1981 | doCheck = false; |
|
1982 | 1982 | propagatedBuildInputs = with self; []; |
|
1983 | 1983 | src = fetchurl { |
|
1984 | 1984 | url = "https://pypi.python.org/packages/b8/98/a90f1d96ffcb15dfc220af524ce23e0a5881258dafa197673357ce1683dd/ws4py-0.4.2.tar.gz"; |
|
1985 | 1985 | md5 = "f0603ae376707a58d205bd87a67758a2"; |
|
1986 | 1986 | }; |
|
1987 | 1987 | meta = { |
|
1988 | 1988 | license = [ pkgs.lib.licenses.bsdOriginal ]; |
|
1989 | 1989 | }; |
|
1990 | 1990 | }; |
|
1991 | 1991 | wsgiref = super.buildPythonPackage { |
|
1992 | 1992 | name = "wsgiref-0.1.2"; |
|
1993 | 1993 | buildInputs = with self; []; |
|
1994 | 1994 | doCheck = false; |
|
1995 | 1995 | propagatedBuildInputs = with self; []; |
|
1996 | 1996 | src = fetchurl { |
|
1997 | 1997 | url = "https://pypi.python.org/packages/41/9e/309259ce8dff8c596e8c26df86dbc4e848b9249fd36797fd60be456f03fc/wsgiref-0.1.2.zip"; |
|
1998 | 1998 | md5 = "29b146e6ebd0f9fb119fe321f7bcf6cb"; |
|
1999 | 1999 | }; |
|
2000 | 2000 | meta = { |
|
2001 | 2001 | license = [ { fullName = "PSF or ZPL"; } ]; |
|
2002 | 2002 | }; |
|
2003 | 2003 | }; |
|
2004 | 2004 | zope.cachedescriptors = super.buildPythonPackage { |
|
2005 | 2005 | name = "zope.cachedescriptors-4.0.0"; |
|
2006 | 2006 | buildInputs = with self; []; |
|
2007 | 2007 | doCheck = false; |
|
2008 | 2008 | propagatedBuildInputs = with self; [setuptools]; |
|
2009 | 2009 | src = fetchurl { |
|
2010 | 2010 | url = "https://pypi.python.org/packages/40/33/694b6644c37f28553f4b9f20b3c3a20fb709a22574dff20b5bdffb09ecd5/zope.cachedescriptors-4.0.0.tar.gz"; |
|
2011 | 2011 | md5 = "8d308de8c936792c8e758058fcb7d0f0"; |
|
2012 | 2012 | }; |
|
2013 | 2013 | meta = { |
|
2014 | 2014 | license = [ pkgs.lib.licenses.zpt21 ]; |
|
2015 | 2015 | }; |
|
2016 | 2016 | }; |
|
2017 | 2017 | zope.deprecation = super.buildPythonPackage { |
|
2018 | 2018 | name = "zope.deprecation-4.1.2"; |
|
2019 | 2019 | buildInputs = with self; []; |
|
2020 | 2020 | doCheck = false; |
|
2021 | 2021 | propagatedBuildInputs = with self; [setuptools]; |
|
2022 | 2022 | src = fetchurl { |
|
2023 | 2023 | url = "https://pypi.python.org/packages/c1/d3/3919492d5e57d8dd01b36f30b34fc8404a30577392b1eb817c303499ad20/zope.deprecation-4.1.2.tar.gz"; |
|
2024 | 2024 | md5 = "e9a663ded58f4f9f7881beb56cae2782"; |
|
2025 | 2025 | }; |
|
2026 | 2026 | meta = { |
|
2027 | 2027 | license = [ pkgs.lib.licenses.zpt21 ]; |
|
2028 | 2028 | }; |
|
2029 | 2029 | }; |
|
2030 | 2030 | zope.event = super.buildPythonPackage { |
|
2031 | 2031 | name = "zope.event-4.0.3"; |
|
2032 | 2032 | buildInputs = with self; []; |
|
2033 | 2033 | doCheck = false; |
|
2034 | 2034 | propagatedBuildInputs = with self; [setuptools]; |
|
2035 | 2035 | src = fetchurl { |
|
2036 | 2036 | url = "https://pypi.python.org/packages/c1/29/91ba884d7d6d96691df592e9e9c2bfa57a47040ec1ff47eff18c85137152/zope.event-4.0.3.tar.gz"; |
|
2037 | 2037 | md5 = "9a3780916332b18b8b85f522bcc3e249"; |
|
2038 | 2038 | }; |
|
2039 | 2039 | meta = { |
|
2040 | 2040 | license = [ pkgs.lib.licenses.zpt21 ]; |
|
2041 | 2041 | }; |
|
2042 | 2042 | }; |
|
2043 | 2043 | zope.interface = super.buildPythonPackage { |
|
2044 | 2044 | name = "zope.interface-4.1.3"; |
|
2045 | 2045 | buildInputs = with self; []; |
|
2046 | 2046 | doCheck = false; |
|
2047 | 2047 | propagatedBuildInputs = with self; [setuptools]; |
|
2048 | 2048 | src = fetchurl { |
|
2049 | 2049 | url = "https://pypi.python.org/packages/9d/81/2509ca3c6f59080123c1a8a97125eb48414022618cec0e64eb1313727bfe/zope.interface-4.1.3.tar.gz"; |
|
2050 | 2050 | md5 = "9ae3d24c0c7415deb249dd1a132f0f79"; |
|
2051 | 2051 | }; |
|
2052 | 2052 | meta = { |
|
2053 | 2053 | license = [ pkgs.lib.licenses.zpt21 ]; |
|
2054 | 2054 | }; |
|
2055 | 2055 | }; |
|
2056 | 2056 | |
|
2057 | 2057 | ### Test requirements |
|
2058 | 2058 | |
|
2059 | 2059 | |
|
2060 | 2060 | } |
@@ -1,134 +1,131 b'' | |||
|
1 | 1 | ## core |
|
2 | 2 | setuptools==30.1.0 |
|
3 |
setuptools-scm==1.15. |
|
|
3 | setuptools-scm==1.15.6 | |
|
4 | 4 | |
|
5 | 5 | amqplib==1.0.2 |
|
6 | anyjson==0.3.3 | |
|
6 | amqp==2.2.2 | |
|
7 | 7 | authomatic==0.1.0.post1 |
|
8 | 8 | Babel==1.3 |
|
9 | 9 | Beaker==1.9.0 |
|
10 |
celery== |
|
|
10 | celery==4.1.0 | |
|
11 | 11 | Chameleon==2.24 |
|
12 | 12 | channelstream==0.5.2 |
|
13 | 13 | click==6.6 |
|
14 | 14 | colander==1.4.0 |
|
15 | 15 | configobj==5.0.6 |
|
16 | 16 | cssselect==1.0.1 |
|
17 | 17 | decorator==4.1.2 |
|
18 | 18 | deform==2.0.4 |
|
19 | 19 | docutils==0.14.0 |
|
20 | 20 | dogpile.cache==0.6.4 |
|
21 | 21 | dogpile.core==0.4.1 |
|
22 | 22 | ecdsa==0.13 |
|
23 | 23 | FormEncode==1.2.4 |
|
24 | 24 | future==0.14.3 |
|
25 | 25 | futures==3.0.2 |
|
26 | 26 | gnureadline==6.3.8 |
|
27 | 27 | infrae.cache==1.0.1 |
|
28 | 28 | iso8601==0.1.12 |
|
29 | 29 | itsdangerous==0.24 |
|
30 | 30 | Jinja2==2.9.6 |
|
31 | kombu==1.5.1 | |
|
31 | billiard==3.5.0.3 | |
|
32 | kombu==4.1.0 | |
|
32 | 33 | lxml==3.7.3 |
|
33 | 34 | Mako==1.0.7 |
|
34 | 35 | Markdown==2.6.9 |
|
35 | 36 | MarkupSafe==1.0.0 |
|
36 | 37 | msgpack-python==0.4.8 |
|
37 | 38 | MySQL-python==1.2.5 |
|
38 | 39 | objgraph==3.1.1 |
|
39 | 40 | packaging==15.2 |
|
40 | 41 | Paste==2.0.3 |
|
41 | 42 | PasteDeploy==1.5.2 |
|
42 | 43 | PasteScript==2.0.2 |
|
43 | 44 | pathlib2==2.3.0 |
|
44 | 45 | peppercorn==0.5 |
|
45 | 46 | psutil==5.4.0 |
|
46 | 47 | psycopg2==2.7.3.2 |
|
47 | 48 | py-bcrypt==0.4 |
|
48 | 49 | pycrypto==2.6.1 |
|
49 | 50 | pycurl==7.19.5 |
|
50 | 51 | pyflakes==0.8.1 |
|
51 | 52 | pygments-markdown-lexer==0.1.0.dev39 |
|
52 | 53 | Pygments==2.2.0 |
|
53 | 54 | pyparsing==1.5.7 |
|
54 | 55 | pyramid-beaker==0.8 |
|
55 | 56 | pyramid-debugtoolbar==4.3.0 |
|
56 | 57 | pyramid-jinja2==2.7 |
|
57 | 58 | pyramid-mako==1.0.2 |
|
58 | 59 | pyramid==1.9.1 |
|
59 | 60 | pysqlite==2.8.3 |
|
60 |
python-dateutil |
|
|
61 | python-dateutil | |
|
61 | 62 | python-ldap==2.4.45 |
|
62 | 63 | python-memcached==1.58 |
|
63 | 64 | python-pam==1.8.2 |
|
64 |
pytz==201 |
|
|
65 | pytz==2017.3 | |
|
65 | 66 | pyzmq==14.6.0 |
|
66 | 67 | py-gfm==0.1.3 |
|
67 | 68 | recaptcha-client==1.0.6 |
|
68 | 69 | redis==2.10.6 |
|
69 | 70 | repoze.lru==0.7 |
|
70 | 71 | requests==2.9.1 |
|
71 |
Routes== |
|
|
72 | Routes==2.4.1 | |
|
72 | 73 | setproctitle==1.1.10 |
|
73 | 74 | simplejson==3.11.1 |
|
74 | 75 | six==1.11.0 |
|
75 | 76 | SQLAlchemy==1.1.15 |
|
76 | 77 | sshpubkeys==2.2.0 |
|
77 | 78 | subprocess32==3.2.7 |
|
78 | 79 | Tempita==0.5.2 |
|
79 | 80 | translationstring==1.3 |
|
80 | 81 | trollius==1.0.4 |
|
81 | 82 | urllib3==1.16 |
|
82 | 83 | URLObject==2.4.0 |
|
83 | 84 | venusian==1.1.0 |
|
84 | 85 | WebError==0.10.3 |
|
85 | 86 | WebHelpers2==2.0 |
|
86 | 87 | WebHelpers==1.3 |
|
87 | 88 | WebOb==1.7.3 |
|
88 | 89 | Whoosh==2.7.4 |
|
89 | 90 | wsgiref==0.1.2 |
|
90 | 91 | zope.cachedescriptors==4.0.0 |
|
91 | 92 | zope.deprecation==4.1.2 |
|
92 | 93 | zope.event==4.0.3 |
|
93 | 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 | 97 | # IPYTHON RENDERING |
|
101 | 98 | # entrypoints backport, pypi version doesn't support egg installs |
|
102 | 99 | https://code.rhodecode.com/upstream/entrypoints/archive/96e6d645684e1af3d7df5b5272f3fe85a546b233.tar.gz?md5=7db37771aea9ac9fefe093e5d6987313#egg=entrypoints==0.2.2.rhodecode-upstream1 |
|
103 | 100 | nbconvert==5.1.1 |
|
104 | 101 | bleach==1.5.0 |
|
105 | 102 | nbformat==4.3.0 |
|
106 | 103 | jupyter_client==5.0.0 |
|
107 | 104 | |
|
108 | 105 | ## cli tools |
|
109 | 106 | alembic==0.9.6 |
|
110 | 107 | invoke==0.13.0 |
|
111 | 108 | bumpversion==0.5.3 |
|
112 | 109 | transifex-client==0.12.5 |
|
113 | 110 | |
|
114 | 111 | ## http servers |
|
115 | 112 | gevent==1.2.2 |
|
116 | 113 | greenlet==0.4.12 |
|
117 | 114 | gunicorn==19.7.1 |
|
118 | 115 | waitress==1.1.0 |
|
119 | 116 | uWSGI==2.0.15 |
|
120 | 117 | |
|
121 | 118 | ## debug |
|
122 | 119 | ipdb==0.10.3 |
|
123 | 120 | ipython==5.1.0 |
|
124 | 121 | CProfileV==1.0.7 |
|
125 | 122 | bottle==0.12.13 |
|
126 | 123 | |
|
127 | 124 | ## rhodecode-tools, special case |
|
128 | 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 | 127 | ## appenlight |
|
131 | 128 | appenlight-client==0.6.22 |
|
132 | 129 | |
|
133 | 130 | ## test related requirements |
|
134 | 131 | -r requirements_test.txt |
@@ -1,52 +1,52 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | |
|
3 | 3 | # Copyright (C) 2010-2017 RhodeCode GmbH |
|
4 | 4 | # |
|
5 | 5 | # This program is free software: you can redistribute it and/or modify |
|
6 | 6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
7 | 7 | # (only), as published by the Free Software Foundation. |
|
8 | 8 | # |
|
9 | 9 | # This program is distributed in the hope that it will be useful, |
|
10 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 | 12 | # GNU General Public License for more details. |
|
13 | 13 | # |
|
14 | 14 | # You should have received a copy of the GNU Affero General Public License |
|
15 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | 16 | # |
|
17 | 17 | # This program is dual-licensed. If you wish to learn more about the |
|
18 | 18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
19 | 19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
20 | 20 | |
|
21 | 21 | import pytest |
|
22 | 22 | |
|
23 | 23 | from rhodecode.model.meta import Session |
|
24 | 24 | from rhodecode.model.user import UserModel |
|
25 | 25 | from rhodecode.model.auth_token import AuthTokenModel |
|
26 | 26 | from rhodecode.tests import TEST_USER_ADMIN_LOGIN |
|
27 | 27 | |
|
28 | 28 | |
|
29 | 29 | @pytest.fixture(scope="class") |
|
30 |
def testuser_api(request, |
|
|
30 | def testuser_api(request, baseapp): | |
|
31 | 31 | cls = request.cls |
|
32 | 32 | |
|
33 | 33 | # ADMIN USER |
|
34 | 34 | cls.usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN) |
|
35 | 35 | cls.apikey = cls.usr.api_key |
|
36 | 36 | |
|
37 | 37 | # REGULAR USER |
|
38 | 38 | cls.test_user = UserModel().create_or_update( |
|
39 | 39 | username='test-api', |
|
40 | 40 | password='test', |
|
41 | 41 | email='test@api.rhodecode.org', |
|
42 | 42 | firstname='first', |
|
43 | 43 | lastname='last' |
|
44 | 44 | ) |
|
45 | 45 | # create TOKEN for user, if he doesn't have one |
|
46 | 46 | if not cls.test_user.api_key: |
|
47 | 47 | AuthTokenModel().create( |
|
48 | 48 | user=cls.test_user, description=u'TEST_USER_TOKEN') |
|
49 | 49 | |
|
50 | 50 | Session().commit() |
|
51 | 51 | cls.TEST_USER_LOGIN = cls.test_user.username |
|
52 | 52 | cls.apikey_regular = cls.test_user.api_key |
@@ -1,582 +1,564 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | |
|
3 | 3 | # Copyright (C) 2016-2017 RhodeCode GmbH |
|
4 | 4 | # |
|
5 | 5 | # This program is free software: you can redistribute it and/or modify |
|
6 | 6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
7 | 7 | # (only), as published by the Free Software Foundation. |
|
8 | 8 | # |
|
9 | 9 | # This program is distributed in the hope that it will be useful, |
|
10 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 | 12 | # GNU General Public License for more details. |
|
13 | 13 | # |
|
14 | 14 | # You should have received a copy of the GNU Affero General Public License |
|
15 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | 16 | # |
|
17 | 17 | # This program is dual-licensed. If you wish to learn more about the |
|
18 | 18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
19 | 19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
20 | 20 | |
|
21 | 21 | import time |
|
22 | 22 | import logging |
|
23 | 23 | import operator |
|
24 | 24 | |
|
25 | 25 | from pyramid.httpexceptions import HTTPFound |
|
26 | 26 | |
|
27 | 27 | from rhodecode.lib import helpers as h |
|
28 | 28 | from rhodecode.lib.utils2 import StrictAttributeDict, safe_int, datetime_to_time |
|
29 | 29 | from rhodecode.lib.vcs.exceptions import RepositoryRequirementError |
|
30 | 30 | from rhodecode.model import repo |
|
31 | 31 | from rhodecode.model import repo_group |
|
32 | 32 | from rhodecode.model import user_group |
|
33 | 33 | from rhodecode.model import user |
|
34 | 34 | from rhodecode.model.db import User |
|
35 | 35 | from rhodecode.model.scm import ScmModel |
|
36 | 36 | |
|
37 | 37 | log = logging.getLogger(__name__) |
|
38 | 38 | |
|
39 | 39 | |
|
40 | 40 | ADMIN_PREFIX = '/_admin' |
|
41 | 41 | STATIC_FILE_PREFIX = '/_static' |
|
42 | 42 | |
|
43 | 43 | URL_NAME_REQUIREMENTS = { |
|
44 | 44 | # group name can have a slash in them, but they must not end with a slash |
|
45 | 45 | 'group_name': r'.*?[^/]', |
|
46 | 46 | 'repo_group_name': r'.*?[^/]', |
|
47 | 47 | # repo names can have a slash in them, but they must not end with a slash |
|
48 | 48 | 'repo_name': r'.*?[^/]', |
|
49 | 49 | # file path eats up everything at the end |
|
50 | 50 | 'f_path': r'.*', |
|
51 | 51 | # reference types |
|
52 | 52 | 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)', |
|
53 | 53 | 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)', |
|
54 | 54 | } |
|
55 | 55 | |
|
56 | 56 | |
|
57 | 57 | def add_route_with_slash(config,name, pattern, **kw): |
|
58 | 58 | config.add_route(name, pattern, **kw) |
|
59 | 59 | if not pattern.endswith('/'): |
|
60 | 60 | config.add_route(name + '_slash', pattern + '/', **kw) |
|
61 | 61 | |
|
62 | 62 | |
|
63 | 63 | def add_route_requirements(route_path, requirements=URL_NAME_REQUIREMENTS): |
|
64 | 64 | """ |
|
65 | 65 | Adds regex requirements to pyramid routes using a mapping dict |
|
66 | 66 | e.g:: |
|
67 | 67 | add_route_requirements('{repo_name}/settings') |
|
68 | 68 | """ |
|
69 | 69 | for key, regex in requirements.items(): |
|
70 | 70 | route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex)) |
|
71 | 71 | return route_path |
|
72 | 72 | |
|
73 | 73 | |
|
74 | 74 | def get_format_ref_id(repo): |
|
75 | 75 | """Returns a `repo` specific reference formatter function""" |
|
76 | 76 | if h.is_svn(repo): |
|
77 | 77 | return _format_ref_id_svn |
|
78 | 78 | else: |
|
79 | 79 | return _format_ref_id |
|
80 | 80 | |
|
81 | 81 | |
|
82 | 82 | def _format_ref_id(name, raw_id): |
|
83 | 83 | """Default formatting of a given reference `name`""" |
|
84 | 84 | return name |
|
85 | 85 | |
|
86 | 86 | |
|
87 | 87 | def _format_ref_id_svn(name, raw_id): |
|
88 | 88 | """Special way of formatting a reference for Subversion including path""" |
|
89 | 89 | return '%s@%s' % (name, raw_id) |
|
90 | 90 | |
|
91 | 91 | |
|
92 | 92 | class TemplateArgs(StrictAttributeDict): |
|
93 | 93 | pass |
|
94 | 94 | |
|
95 | 95 | |
|
96 | 96 | class BaseAppView(object): |
|
97 | 97 | |
|
98 | 98 | def __init__(self, context, request): |
|
99 | 99 | self.request = request |
|
100 | 100 | self.context = context |
|
101 | 101 | self.session = request.session |
|
102 | 102 | self._rhodecode_user = request.user # auth user |
|
103 | 103 | self._rhodecode_db_user = self._rhodecode_user.get_instance() |
|
104 | 104 | self._maybe_needs_password_change( |
|
105 | 105 | request.matched_route.name, self._rhodecode_db_user) |
|
106 | 106 | |
|
107 | 107 | def _maybe_needs_password_change(self, view_name, user_obj): |
|
108 | 108 | log.debug('Checking if user %s needs password change on view %s', |
|
109 | 109 | user_obj, view_name) |
|
110 | 110 | skip_user_views = [ |
|
111 | 111 | 'logout', 'login', |
|
112 | 112 | 'my_account_password', 'my_account_password_update' |
|
113 | 113 | ] |
|
114 | 114 | |
|
115 | 115 | if not user_obj: |
|
116 | 116 | return |
|
117 | 117 | |
|
118 | 118 | if user_obj.username == User.DEFAULT_USER: |
|
119 | 119 | return |
|
120 | 120 | |
|
121 | 121 | now = time.time() |
|
122 | 122 | should_change = user_obj.user_data.get('force_password_change') |
|
123 | 123 | change_after = safe_int(should_change) or 0 |
|
124 | 124 | if should_change and now > change_after: |
|
125 | 125 | log.debug('User %s requires password change', user_obj) |
|
126 | 126 | h.flash('You are required to change your password', 'warning', |
|
127 | 127 | ignore_duplicate=True) |
|
128 | 128 | |
|
129 | 129 | if view_name not in skip_user_views: |
|
130 | 130 | raise HTTPFound( |
|
131 | 131 | self.request.route_path('my_account_password')) |
|
132 | 132 | |
|
133 | 133 | def _log_creation_exception(self, e, repo_name): |
|
134 | 134 | _ = self.request.translate |
|
135 | 135 | reason = None |
|
136 | 136 | if len(e.args) == 2: |
|
137 | 137 | reason = e.args[1] |
|
138 | 138 | |
|
139 | 139 | if reason == 'INVALID_CERTIFICATE': |
|
140 | 140 | log.exception( |
|
141 | 141 | 'Exception creating a repository: invalid certificate') |
|
142 | 142 | msg = (_('Error creating repository %s: invalid certificate') |
|
143 | 143 | % repo_name) |
|
144 | 144 | else: |
|
145 | 145 | log.exception("Exception creating a repository") |
|
146 | 146 | msg = (_('Error creating repository %s') |
|
147 | 147 | % repo_name) |
|
148 | 148 | return msg |
|
149 | 149 | |
|
150 | 150 | def _get_local_tmpl_context(self, include_app_defaults=True): |
|
151 | 151 | c = TemplateArgs() |
|
152 | 152 | c.auth_user = self.request.user |
|
153 | 153 | # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user |
|
154 | 154 | c.rhodecode_user = self.request.user |
|
155 | 155 | |
|
156 | 156 | if include_app_defaults: |
|
157 | 157 | from rhodecode.lib.base import attach_context_attributes |
|
158 | 158 | attach_context_attributes(c, self.request, self.request.user.user_id) |
|
159 | 159 | |
|
160 | 160 | return c |
|
161 | 161 | |
|
162 |
def _ |
|
|
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) | |
|
162 | def _get_template_context(self, tmpl_args, **kwargs): | |
|
178 | 163 | |
|
179 | 164 | local_tmpl_args = { |
|
180 | 165 | 'defaults': {}, |
|
181 | 166 | 'errors': {}, |
|
182 | # register a fake 'c' to be used in templates instead of global | |
|
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 | |
|
167 | 'c': tmpl_args | |
|
186 | 168 | } |
|
187 |
local_tmpl_args.update( |
|
|
169 | local_tmpl_args.update(kwargs) | |
|
188 | 170 | return local_tmpl_args |
|
189 | 171 | |
|
190 | 172 | def load_default_context(self): |
|
191 | 173 | """ |
|
192 | 174 | example: |
|
193 | 175 | |
|
194 | 176 | def load_default_context(self): |
|
195 | 177 | c = self._get_local_tmpl_context() |
|
196 | 178 | c.custom_var = 'foobar' |
|
197 | self._register_global_c(c) | |
|
179 | ||
|
198 | 180 | return c |
|
199 | 181 | """ |
|
200 | 182 | raise NotImplementedError('Needs implementation in view class') |
|
201 | 183 | |
|
202 | 184 | |
|
203 | 185 | class RepoAppView(BaseAppView): |
|
204 | 186 | |
|
205 | 187 | def __init__(self, context, request): |
|
206 | 188 | super(RepoAppView, self).__init__(context, request) |
|
207 | 189 | self.db_repo = request.db_repo |
|
208 | 190 | self.db_repo_name = self.db_repo.repo_name |
|
209 | 191 | self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo) |
|
210 | 192 | |
|
211 | 193 | def _handle_missing_requirements(self, error): |
|
212 | 194 | log.error( |
|
213 | 195 | 'Requirements are missing for repository %s: %s', |
|
214 | 196 | self.db_repo_name, error.message) |
|
215 | 197 | |
|
216 |
def _get_local_tmpl_context(self, include_app_defaults= |
|
|
198 | def _get_local_tmpl_context(self, include_app_defaults=True): | |
|
217 | 199 | _ = self.request.translate |
|
218 | 200 | c = super(RepoAppView, self)._get_local_tmpl_context( |
|
219 | 201 | include_app_defaults=include_app_defaults) |
|
220 | 202 | |
|
221 | 203 | # register common vars for this type of view |
|
222 | 204 | c.rhodecode_db_repo = self.db_repo |
|
223 | 205 | c.repo_name = self.db_repo_name |
|
224 | 206 | c.repository_pull_requests = self.db_repo_pull_requests |
|
225 | 207 | |
|
226 | 208 | c.repository_requirements_missing = False |
|
227 | 209 | try: |
|
228 | 210 | self.rhodecode_vcs_repo = self.db_repo.scm_instance() |
|
229 | 211 | except RepositoryRequirementError as e: |
|
230 | 212 | c.repository_requirements_missing = True |
|
231 | 213 | self._handle_missing_requirements(e) |
|
232 | 214 | self.rhodecode_vcs_repo = None |
|
233 | 215 | |
|
234 | 216 | if (not c.repository_requirements_missing |
|
235 | 217 | and self.rhodecode_vcs_repo is None): |
|
236 | 218 | # unable to fetch this repo as vcs instance, report back to user |
|
237 | 219 | h.flash(_( |
|
238 | 220 | "The repository `%(repo_name)s` cannot be loaded in filesystem. " |
|
239 | 221 | "Please check if it exist, or is not damaged.") % |
|
240 | 222 | {'repo_name': c.repo_name}, |
|
241 | 223 | category='error', ignore_duplicate=True) |
|
242 | 224 | raise HTTPFound(h.route_path('home')) |
|
243 | 225 | |
|
244 | 226 | return c |
|
245 | 227 | |
|
246 | 228 | def _get_f_path(self, matchdict, default=None): |
|
247 | 229 | f_path = matchdict.get('f_path') |
|
248 | 230 | if f_path: |
|
249 | 231 | # fix for multiple initial slashes that causes errors for GIT |
|
250 | 232 | return f_path.lstrip('/') |
|
251 | 233 | |
|
252 | 234 | return default |
|
253 | 235 | |
|
254 | 236 | |
|
255 | 237 | class RepoGroupAppView(BaseAppView): |
|
256 | 238 | def __init__(self, context, request): |
|
257 | 239 | super(RepoGroupAppView, self).__init__(context, request) |
|
258 | 240 | self.db_repo_group = request.db_repo_group |
|
259 | 241 | self.db_repo_group_name = self.db_repo_group.group_name |
|
260 | 242 | |
|
261 | 243 | def _revoke_perms_on_yourself(self, form_result): |
|
262 | 244 | _updates = filter(lambda u: self._rhodecode_user.user_id == int(u[0]), |
|
263 | 245 | form_result['perm_updates']) |
|
264 | 246 | _additions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]), |
|
265 | 247 | form_result['perm_additions']) |
|
266 | 248 | _deletions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]), |
|
267 | 249 | form_result['perm_deletions']) |
|
268 | 250 | admin_perm = 'group.admin' |
|
269 | 251 | if _updates and _updates[0][1] != admin_perm or \ |
|
270 | 252 | _additions and _additions[0][1] != admin_perm or \ |
|
271 | 253 | _deletions and _deletions[0][1] != admin_perm: |
|
272 | 254 | return True |
|
273 | 255 | return False |
|
274 | 256 | |
|
275 | 257 | |
|
276 | 258 | class UserGroupAppView(BaseAppView): |
|
277 | 259 | def __init__(self, context, request): |
|
278 | 260 | super(UserGroupAppView, self).__init__(context, request) |
|
279 | 261 | self.db_user_group = request.db_user_group |
|
280 | 262 | self.db_user_group_name = self.db_user_group.users_group_name |
|
281 | 263 | |
|
282 | 264 | |
|
283 | 265 | class UserAppView(BaseAppView): |
|
284 | 266 | def __init__(self, context, request): |
|
285 | 267 | super(UserAppView, self).__init__(context, request) |
|
286 | 268 | self.db_user = request.db_user |
|
287 | 269 | self.db_user_id = self.db_user.user_id |
|
288 | 270 | |
|
289 | 271 | _ = self.request.translate |
|
290 | 272 | if not request.db_user_supports_default: |
|
291 | 273 | if self.db_user.username == User.DEFAULT_USER: |
|
292 | 274 | h.flash(_("Editing user `{}` is disabled.".format( |
|
293 | 275 | User.DEFAULT_USER)), category='warning') |
|
294 | 276 | raise HTTPFound(h.route_path('users')) |
|
295 | 277 | |
|
296 | 278 | |
|
297 | 279 | class DataGridAppView(object): |
|
298 | 280 | """ |
|
299 | 281 | Common class to have re-usable grid rendering components |
|
300 | 282 | """ |
|
301 | 283 | |
|
302 | 284 | def _extract_ordering(self, request, column_map=None): |
|
303 | 285 | column_map = column_map or {} |
|
304 | 286 | column_index = safe_int(request.GET.get('order[0][column]')) |
|
305 | 287 | order_dir = request.GET.get( |
|
306 | 288 | 'order[0][dir]', 'desc') |
|
307 | 289 | order_by = request.GET.get( |
|
308 | 290 | 'columns[%s][data][sort]' % column_index, 'name_raw') |
|
309 | 291 | |
|
310 | 292 | # translate datatable to DB columns |
|
311 | 293 | order_by = column_map.get(order_by) or order_by |
|
312 | 294 | |
|
313 | 295 | search_q = request.GET.get('search[value]') |
|
314 | 296 | return search_q, order_by, order_dir |
|
315 | 297 | |
|
316 | 298 | def _extract_chunk(self, request): |
|
317 | 299 | start = safe_int(request.GET.get('start'), 0) |
|
318 | 300 | length = safe_int(request.GET.get('length'), 25) |
|
319 | 301 | draw = safe_int(request.GET.get('draw')) |
|
320 | 302 | return draw, start, length |
|
321 | 303 | |
|
322 | 304 | def _get_order_col(self, order_by, model): |
|
323 | 305 | if isinstance(order_by, basestring): |
|
324 | 306 | try: |
|
325 | 307 | return operator.attrgetter(order_by)(model) |
|
326 | 308 | except AttributeError: |
|
327 | 309 | return None |
|
328 | 310 | else: |
|
329 | 311 | return order_by |
|
330 | 312 | |
|
331 | 313 | |
|
332 | 314 | class BaseReferencesView(RepoAppView): |
|
333 | 315 | """ |
|
334 | 316 | Base for reference view for branches, tags and bookmarks. |
|
335 | 317 | """ |
|
336 | 318 | def load_default_context(self): |
|
337 | 319 | c = self._get_local_tmpl_context() |
|
338 | 320 | |
|
339 | self._register_global_c(c) | |
|
321 | ||
|
340 | 322 | return c |
|
341 | 323 | |
|
342 | 324 | def load_refs_context(self, ref_items, partials_template): |
|
343 | 325 | _render = self.request.get_partial_renderer(partials_template) |
|
344 | 326 | pre_load = ["author", "date", "message"] |
|
345 | 327 | |
|
346 | 328 | is_svn = h.is_svn(self.rhodecode_vcs_repo) |
|
347 | 329 | is_hg = h.is_hg(self.rhodecode_vcs_repo) |
|
348 | 330 | |
|
349 | 331 | format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo) |
|
350 | 332 | |
|
351 | 333 | closed_refs = {} |
|
352 | 334 | if is_hg: |
|
353 | 335 | closed_refs = self.rhodecode_vcs_repo.branches_closed |
|
354 | 336 | |
|
355 | 337 | data = [] |
|
356 | 338 | for ref_name, commit_id in ref_items: |
|
357 | 339 | commit = self.rhodecode_vcs_repo.get_commit( |
|
358 | 340 | commit_id=commit_id, pre_load=pre_load) |
|
359 | 341 | closed = ref_name in closed_refs |
|
360 | 342 | |
|
361 | 343 | # TODO: johbo: Unify generation of reference links |
|
362 | 344 | use_commit_id = '/' in ref_name or is_svn |
|
363 | 345 | |
|
364 | 346 | if use_commit_id: |
|
365 | 347 | files_url = h.route_path( |
|
366 | 348 | 'repo_files', |
|
367 | 349 | repo_name=self.db_repo_name, |
|
368 | 350 | f_path=ref_name if is_svn else '', |
|
369 | 351 | commit_id=commit_id) |
|
370 | 352 | |
|
371 | 353 | else: |
|
372 | 354 | files_url = h.route_path( |
|
373 | 355 | 'repo_files', |
|
374 | 356 | repo_name=self.db_repo_name, |
|
375 | 357 | f_path=ref_name if is_svn else '', |
|
376 | 358 | commit_id=ref_name, |
|
377 | 359 | _query=dict(at=ref_name)) |
|
378 | 360 | |
|
379 | 361 | data.append({ |
|
380 | 362 | "name": _render('name', ref_name, files_url, closed), |
|
381 | 363 | "name_raw": ref_name, |
|
382 | 364 | "date": _render('date', commit.date), |
|
383 | 365 | "date_raw": datetime_to_time(commit.date), |
|
384 | 366 | "author": _render('author', commit.author), |
|
385 | 367 | "commit": _render( |
|
386 | 368 | 'commit', commit.message, commit.raw_id, commit.idx), |
|
387 | 369 | "commit_raw": commit.idx, |
|
388 | 370 | "compare": _render( |
|
389 | 371 | 'compare', format_ref_id(ref_name, commit.raw_id)), |
|
390 | 372 | }) |
|
391 | 373 | |
|
392 | 374 | return data |
|
393 | 375 | |
|
394 | 376 | |
|
395 | 377 | class RepoRoutePredicate(object): |
|
396 | 378 | def __init__(self, val, config): |
|
397 | 379 | self.val = val |
|
398 | 380 | |
|
399 | 381 | def text(self): |
|
400 | 382 | return 'repo_route = %s' % self.val |
|
401 | 383 | |
|
402 | 384 | phash = text |
|
403 | 385 | |
|
404 | 386 | def __call__(self, info, request): |
|
405 | 387 | |
|
406 | 388 | if hasattr(request, 'vcs_call'): |
|
407 | 389 | # skip vcs calls |
|
408 | 390 | return |
|
409 | 391 | |
|
410 | 392 | repo_name = info['match']['repo_name'] |
|
411 | 393 | repo_model = repo.RepoModel() |
|
412 | 394 | by_name_match = repo_model.get_by_repo_name(repo_name, cache=True) |
|
413 | 395 | |
|
414 | 396 | def redirect_if_creating(db_repo): |
|
415 | 397 | if db_repo.repo_state in [repo.Repository.STATE_PENDING]: |
|
416 | 398 | raise HTTPFound( |
|
417 | 399 | request.route_path('repo_creating', |
|
418 | 400 | repo_name=db_repo.repo_name)) |
|
419 | 401 | |
|
420 | 402 | if by_name_match: |
|
421 | 403 | # register this as request object we can re-use later |
|
422 | 404 | request.db_repo = by_name_match |
|
423 | 405 | redirect_if_creating(by_name_match) |
|
424 | 406 | return True |
|
425 | 407 | |
|
426 | 408 | by_id_match = repo_model.get_repo_by_id(repo_name) |
|
427 | 409 | if by_id_match: |
|
428 | 410 | request.db_repo = by_id_match |
|
429 | 411 | redirect_if_creating(by_id_match) |
|
430 | 412 | return True |
|
431 | 413 | |
|
432 | 414 | return False |
|
433 | 415 | |
|
434 | 416 | |
|
435 | 417 | class RepoTypeRoutePredicate(object): |
|
436 | 418 | def __init__(self, val, config): |
|
437 | 419 | self.val = val or ['hg', 'git', 'svn'] |
|
438 | 420 | |
|
439 | 421 | def text(self): |
|
440 | 422 | return 'repo_accepted_type = %s' % self.val |
|
441 | 423 | |
|
442 | 424 | phash = text |
|
443 | 425 | |
|
444 | 426 | def __call__(self, info, request): |
|
445 | 427 | if hasattr(request, 'vcs_call'): |
|
446 | 428 | # skip vcs calls |
|
447 | 429 | return |
|
448 | 430 | |
|
449 | 431 | rhodecode_db_repo = request.db_repo |
|
450 | 432 | |
|
451 | 433 | log.debug( |
|
452 | 434 | '%s checking repo type for %s in %s', |
|
453 | 435 | self.__class__.__name__, rhodecode_db_repo.repo_type, self.val) |
|
454 | 436 | |
|
455 | 437 | if rhodecode_db_repo.repo_type in self.val: |
|
456 | 438 | return True |
|
457 | 439 | else: |
|
458 | 440 | log.warning('Current view is not supported for repo type:%s', |
|
459 | 441 | rhodecode_db_repo.repo_type) |
|
460 | 442 | # |
|
461 | 443 | # h.flash(h.literal( |
|
462 | 444 | # _('Action not supported for %s.' % rhodecode_repo.alias)), |
|
463 | 445 | # category='warning') |
|
464 | 446 | # return redirect( |
|
465 | 447 | # route_path('repo_summary', repo_name=cls.rhodecode_db_repo.repo_name)) |
|
466 | 448 | |
|
467 | 449 | return False |
|
468 | 450 | |
|
469 | 451 | |
|
470 | 452 | class RepoGroupRoutePredicate(object): |
|
471 | 453 | def __init__(self, val, config): |
|
472 | 454 | self.val = val |
|
473 | 455 | |
|
474 | 456 | def text(self): |
|
475 | 457 | return 'repo_group_route = %s' % self.val |
|
476 | 458 | |
|
477 | 459 | phash = text |
|
478 | 460 | |
|
479 | 461 | def __call__(self, info, request): |
|
480 | 462 | if hasattr(request, 'vcs_call'): |
|
481 | 463 | # skip vcs calls |
|
482 | 464 | return |
|
483 | 465 | |
|
484 | 466 | repo_group_name = info['match']['repo_group_name'] |
|
485 | 467 | repo_group_model = repo_group.RepoGroupModel() |
|
486 | 468 | by_name_match = repo_group_model.get_by_group_name( |
|
487 | 469 | repo_group_name, cache=True) |
|
488 | 470 | |
|
489 | 471 | if by_name_match: |
|
490 | 472 | # register this as request object we can re-use later |
|
491 | 473 | request.db_repo_group = by_name_match |
|
492 | 474 | return True |
|
493 | 475 | |
|
494 | 476 | return False |
|
495 | 477 | |
|
496 | 478 | |
|
497 | 479 | class UserGroupRoutePredicate(object): |
|
498 | 480 | def __init__(self, val, config): |
|
499 | 481 | self.val = val |
|
500 | 482 | |
|
501 | 483 | def text(self): |
|
502 | 484 | return 'user_group_route = %s' % self.val |
|
503 | 485 | |
|
504 | 486 | phash = text |
|
505 | 487 | |
|
506 | 488 | def __call__(self, info, request): |
|
507 | 489 | if hasattr(request, 'vcs_call'): |
|
508 | 490 | # skip vcs calls |
|
509 | 491 | return |
|
510 | 492 | |
|
511 | 493 | user_group_id = info['match']['user_group_id'] |
|
512 | 494 | user_group_model = user_group.UserGroup() |
|
513 | 495 | by_id_match = user_group_model.get( |
|
514 | 496 | user_group_id, cache=True) |
|
515 | 497 | |
|
516 | 498 | if by_id_match: |
|
517 | 499 | # register this as request object we can re-use later |
|
518 | 500 | request.db_user_group = by_id_match |
|
519 | 501 | return True |
|
520 | 502 | |
|
521 | 503 | return False |
|
522 | 504 | |
|
523 | 505 | |
|
524 | 506 | class UserRoutePredicateBase(object): |
|
525 | 507 | supports_default = None |
|
526 | 508 | |
|
527 | 509 | def __init__(self, val, config): |
|
528 | 510 | self.val = val |
|
529 | 511 | |
|
530 | 512 | def text(self): |
|
531 | 513 | raise NotImplementedError() |
|
532 | 514 | |
|
533 | 515 | def __call__(self, info, request): |
|
534 | 516 | if hasattr(request, 'vcs_call'): |
|
535 | 517 | # skip vcs calls |
|
536 | 518 | return |
|
537 | 519 | |
|
538 | 520 | user_id = info['match']['user_id'] |
|
539 | 521 | user_model = user.User() |
|
540 | 522 | by_id_match = user_model.get( |
|
541 | 523 | user_id, cache=True) |
|
542 | 524 | |
|
543 | 525 | if by_id_match: |
|
544 | 526 | # register this as request object we can re-use later |
|
545 | 527 | request.db_user = by_id_match |
|
546 | 528 | request.db_user_supports_default = self.supports_default |
|
547 | 529 | return True |
|
548 | 530 | |
|
549 | 531 | return False |
|
550 | 532 | |
|
551 | 533 | |
|
552 | 534 | class UserRoutePredicate(UserRoutePredicateBase): |
|
553 | 535 | supports_default = False |
|
554 | 536 | |
|
555 | 537 | def text(self): |
|
556 | 538 | return 'user_route = %s' % self.val |
|
557 | 539 | |
|
558 | 540 | phash = text |
|
559 | 541 | |
|
560 | 542 | |
|
561 | 543 | class UserRouteWithDefaultPredicate(UserRoutePredicateBase): |
|
562 | 544 | supports_default = True |
|
563 | 545 | |
|
564 | 546 | def text(self): |
|
565 | 547 | return 'user_with_default_route = %s' % self.val |
|
566 | 548 | |
|
567 | 549 | phash = text |
|
568 | 550 | |
|
569 | 551 | |
|
570 | 552 | def includeme(config): |
|
571 | 553 | config.add_route_predicate( |
|
572 | 554 | 'repo_route', RepoRoutePredicate) |
|
573 | 555 | config.add_route_predicate( |
|
574 | 556 | 'repo_accepted_types', RepoTypeRoutePredicate) |
|
575 | 557 | config.add_route_predicate( |
|
576 | 558 | 'repo_group_route', RepoGroupRoutePredicate) |
|
577 | 559 | config.add_route_predicate( |
|
578 | 560 | 'user_group_route', UserGroupRoutePredicate) |
|
579 | 561 | config.add_route_predicate( |
|
580 | 562 | 'user_route_with_default', UserRouteWithDefaultPredicate) |
|
581 | 563 | config.add_route_predicate( |
|
582 | 564 | 'user_route', UserRoutePredicate) No newline at end of file |
@@ -1,136 +1,135 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | |
|
3 | 3 | # Copyright (C) 2016-2017 RhodeCode GmbH |
|
4 | 4 | # |
|
5 | 5 | # This program is free software: you can redistribute it and/or modify |
|
6 | 6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
7 | 7 | # (only), as published by the Free Software Foundation. |
|
8 | 8 | # |
|
9 | 9 | # This program is distributed in the hope that it will be useful, |
|
10 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 | 12 | # GNU General Public License for more details. |
|
13 | 13 | # |
|
14 | 14 | # You should have received a copy of the GNU Affero General Public License |
|
15 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | 16 | # |
|
17 | 17 | # This program is dual-licensed. If you wish to learn more about the |
|
18 | 18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
19 | 19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
20 | 20 | |
|
21 | 21 | |
|
22 | 22 | import logging |
|
23 | 23 | import collections |
|
24 | 24 | |
|
25 | 25 | from zope.interface import implementer |
|
26 | 26 | |
|
27 | 27 | from rhodecode.apps.admin.interfaces import IAdminNavigationRegistry |
|
28 | from rhodecode.lib.utils import get_registry | |
|
29 | 28 | from rhodecode.lib.utils2 import str2bool |
|
30 | 29 | from rhodecode.translation import _ |
|
31 | 30 | |
|
32 | 31 | |
|
33 | 32 | log = logging.getLogger(__name__) |
|
34 | 33 | |
|
35 | 34 | NavListEntry = collections.namedtuple('NavListEntry', ['key', 'name', 'url']) |
|
36 | 35 | |
|
37 | 36 | |
|
38 | 37 | class NavEntry(object): |
|
39 | 38 | """ |
|
40 | 39 | Represents an entry in the admin navigation. |
|
41 | 40 | |
|
42 | 41 | :param key: Unique identifier used to store reference in an OrderedDict. |
|
43 | 42 | :param name: Display name, usually a translation string. |
|
44 | 43 | :param view_name: Name of the view, used generate the URL. |
|
45 | 44 | :param pyramid: Indicator to use pyramid for URL generation. This should |
|
46 | 45 | be removed as soon as we are fully migrated to pyramid. |
|
47 | 46 | """ |
|
48 | 47 | |
|
49 | 48 | def __init__(self, key, name, view_name): |
|
50 | 49 | self.key = key |
|
51 | 50 | self.name = name |
|
52 | 51 | self.view_name = view_name |
|
53 | 52 | |
|
54 | 53 | def generate_url(self, request): |
|
55 | 54 | return request.route_path(self.view_name) |
|
56 | 55 | |
|
57 | 56 | def get_localized_name(self, request): |
|
58 | 57 | return request.translate(self.name) |
|
59 | 58 | |
|
60 | 59 | |
|
61 | 60 | @implementer(IAdminNavigationRegistry) |
|
62 | 61 | class NavigationRegistry(object): |
|
63 | 62 | |
|
64 | 63 | _base_entries = [ |
|
65 | 64 | NavEntry('global', _('Global'), |
|
66 | 65 | 'admin_settings_global'), |
|
67 | 66 | NavEntry('vcs', _('VCS'), |
|
68 | 67 | 'admin_settings_vcs'), |
|
69 | 68 | NavEntry('visual', _('Visual'), |
|
70 | 69 | 'admin_settings_visual'), |
|
71 | 70 | NavEntry('mapping', _('Remap and Rescan'), |
|
72 | 71 | 'admin_settings_mapping'), |
|
73 | 72 | NavEntry('issuetracker', _('Issue Tracker'), |
|
74 | 73 | 'admin_settings_issuetracker'), |
|
75 | 74 | NavEntry('email', _('Email'), |
|
76 | 75 | 'admin_settings_email'), |
|
77 | 76 | NavEntry('hooks', _('Hooks'), |
|
78 | 77 | 'admin_settings_hooks'), |
|
79 | 78 | NavEntry('search', _('Full Text Search'), |
|
80 | 79 | 'admin_settings_search'), |
|
81 | 80 | NavEntry('integrations', _('Integrations'), |
|
82 | 81 | 'global_integrations_home'), |
|
83 | 82 | NavEntry('system', _('System Info'), |
|
84 | 83 | 'admin_settings_system'), |
|
85 | 84 | NavEntry('process_management', _('Processes'), |
|
86 | 85 | 'admin_settings_process_management'), |
|
87 | 86 | NavEntry('sessions', _('User Sessions'), |
|
88 | 87 | 'admin_settings_sessions'), |
|
89 | 88 | NavEntry('open_source', _('Open Source Licenses'), |
|
90 | 89 | 'admin_settings_open_source'), |
|
91 | 90 | |
|
92 | 91 | ] |
|
93 | 92 | |
|
94 | 93 | _labs_entry = NavEntry('labs', _('Labs'), |
|
95 | 94 | 'admin_settings_labs') |
|
96 | 95 | |
|
97 | 96 | def __init__(self, labs_active=False): |
|
98 | 97 | self._registered_entries = collections.OrderedDict() |
|
99 | 98 | for item in self.__class__._base_entries: |
|
100 | 99 | self._registered_entries[item.key] = item |
|
101 | 100 | |
|
102 | 101 | if labs_active: |
|
103 | 102 | self.add_entry(self._labs_entry) |
|
104 | 103 | |
|
105 | 104 | def add_entry(self, entry): |
|
106 | 105 | self._registered_entries[entry.key] = entry |
|
107 | 106 | |
|
108 | 107 | def get_navlist(self, request): |
|
109 | 108 | navlist = [NavListEntry(i.key, i.get_localized_name(request), |
|
110 | 109 | i.generate_url(request)) |
|
111 | 110 | for i in self._registered_entries.values()] |
|
112 | 111 | return navlist |
|
113 | 112 | |
|
114 | 113 | |
|
115 | 114 | def navigation_registry(request, registry=None): |
|
116 | 115 | """ |
|
117 | 116 | Helper that returns the admin navigation registry. |
|
118 | 117 | """ |
|
119 |
pyramid_registry = registry or |
|
|
118 | pyramid_registry = registry or request.registry | |
|
120 | 119 | nav_registry = pyramid_registry.queryUtility(IAdminNavigationRegistry) |
|
121 | 120 | return nav_registry |
|
122 | 121 | |
|
123 | 122 | |
|
124 | 123 | def navigation_list(request): |
|
125 | 124 | """ |
|
126 | 125 | Helper that returns the admin navigation as list of NavListEntry objects. |
|
127 | 126 | """ |
|
128 | 127 | return navigation_registry(request).get_navlist(request) |
|
129 | 128 | |
|
130 | 129 | |
|
131 | 130 | def includeme(config): |
|
132 | 131 | # Create admin navigation registry and add it to the pyramid registry. |
|
133 | 132 | settings = config.get_settings() |
|
134 | 133 | labs_active = str2bool(settings.get('labs_settings_active', False)) |
|
135 | 134 | navigation_registry = NavigationRegistry(labs_active=labs_active) |
|
136 | 135 | config.registry.registerUtility(navigation_registry) No newline at end of file |
@@ -1,187 +1,171 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | |
|
3 | 3 | # Copyright (C) 2010-2017 RhodeCode GmbH |
|
4 | 4 | # |
|
5 | 5 | # This program is free software: you can redistribute it and/or modify |
|
6 | 6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
7 | 7 | # (only), as published by the Free Software Foundation. |
|
8 | 8 | # |
|
9 | 9 | # This program is distributed in the hope that it will be useful, |
|
10 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 | 12 | # GNU General Public License for more details. |
|
13 | 13 | # |
|
14 | 14 | # You should have received a copy of the GNU Affero General Public License |
|
15 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | 16 | # |
|
17 | 17 | # This program is dual-licensed. If you wish to learn more about the |
|
18 | 18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
19 | 19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
20 | 20 | |
|
21 | 21 | import os |
|
22 | 22 | import csv |
|
23 | 23 | import datetime |
|
24 | 24 | |
|
25 | 25 | import pytest |
|
26 | 26 | |
|
27 | 27 | from rhodecode.tests import * |
|
28 | 28 | from rhodecode.tests.fixture import FIXTURES |
|
29 | 29 | from rhodecode.model.db import UserLog |
|
30 | 30 | from rhodecode.model.meta import Session |
|
31 | 31 | from rhodecode.lib.utils2 import safe_unicode |
|
32 | 32 | |
|
33 | 33 | |
|
34 | 34 | def route_path(name, params=None, **kwargs): |
|
35 | 35 | import urllib |
|
36 | 36 | from rhodecode.apps._base import ADMIN_PREFIX |
|
37 | 37 | |
|
38 | 38 | base_url = { |
|
39 | 39 | 'admin_home': ADMIN_PREFIX, |
|
40 | 40 | 'admin_audit_logs': ADMIN_PREFIX + '/audit_logs', |
|
41 | 41 | |
|
42 | 42 | }[name].format(**kwargs) |
|
43 | 43 | |
|
44 | 44 | if params: |
|
45 | 45 | base_url = '{}?{}'.format(base_url, urllib.urlencode(params)) |
|
46 | 46 | return base_url |
|
47 | 47 | |
|
48 | 48 | |
|
49 | class TestAdminController(TestController): | |
|
49 | @pytest.mark.usefixtures('app') | |
|
50 | class TestAdminController(object): | |
|
50 | 51 | |
|
51 | 52 | @pytest.fixture(scope='class', autouse=True) |
|
52 |
def prepare(self, request, |
|
|
53 | def prepare(self, request, baseapp): | |
|
53 | 54 | UserLog.query().delete() |
|
54 | 55 | Session().commit() |
|
55 | 56 | |
|
56 | 57 | def strptime(val): |
|
57 | 58 | fmt = '%Y-%m-%d %H:%M:%S' |
|
58 | 59 | if '.' not in val: |
|
59 | 60 | return datetime.datetime.strptime(val, fmt) |
|
60 | 61 | |
|
61 | 62 | nofrag, frag = val.split(".") |
|
62 | 63 | date = datetime.datetime.strptime(nofrag, fmt) |
|
63 | 64 | |
|
64 | 65 | frag = frag[:6] # truncate to microseconds |
|
65 | 66 | frag += (6 - len(frag)) * '0' # add 0s |
|
66 | 67 | return date.replace(microsecond=int(frag)) |
|
67 | 68 | |
|
68 | 69 | with open(os.path.join(FIXTURES, 'journal_dump.csv')) as f: |
|
69 | 70 | for row in csv.DictReader(f): |
|
70 | 71 | ul = UserLog() |
|
71 | 72 | for k, v in row.iteritems(): |
|
72 | 73 | v = safe_unicode(v) |
|
73 | 74 | if k == 'action_date': |
|
74 | 75 | v = strptime(v) |
|
75 | 76 | if k in ['user_id', 'repository_id']: |
|
76 | 77 | # nullable due to FK problems |
|
77 | 78 | v = None |
|
78 | 79 | setattr(ul, k, v) |
|
79 | 80 | Session().add(ul) |
|
80 | 81 | Session().commit() |
|
81 | 82 | |
|
82 | 83 | @request.addfinalizer |
|
83 | 84 | def cleanup(): |
|
84 | 85 | UserLog.query().delete() |
|
85 | 86 | Session().commit() |
|
86 | 87 | |
|
87 | def test_index(self): | |
|
88 | self.log_user() | |
|
88 | def test_index(self, autologin_user): | |
|
89 | 89 | response = self.app.get(route_path('admin_audit_logs')) |
|
90 | 90 | response.mustcontain('Admin audit logs') |
|
91 | 91 | |
|
92 | def test_filter_all_entries(self): | |
|
93 | self.log_user() | |
|
92 | def test_filter_all_entries(self, autologin_user): | |
|
94 | 93 | response = self.app.get(route_path('admin_audit_logs')) |
|
95 | 94 | all_count = UserLog.query().count() |
|
96 | 95 | response.mustcontain('%s entries' % all_count) |
|
97 | 96 | |
|
98 | def test_filter_journal_filter_exact_match_on_repository(self): | |
|
99 | self.log_user() | |
|
97 | def test_filter_journal_filter_exact_match_on_repository(self, autologin_user): | |
|
100 | 98 | response = self.app.get(route_path('admin_audit_logs', |
|
101 | 99 | params=dict(filter='repository:rhodecode'))) |
|
102 | 100 | response.mustcontain('3 entries') |
|
103 | 101 | |
|
104 | def test_filter_journal_filter_exact_match_on_repository_CamelCase(self): | |
|
105 | self.log_user() | |
|
102 | def test_filter_journal_filter_exact_match_on_repository_CamelCase(self, autologin_user): | |
|
106 | 103 | response = self.app.get(route_path('admin_audit_logs', |
|
107 | 104 | params=dict(filter='repository:RhodeCode'))) |
|
108 | 105 | response.mustcontain('3 entries') |
|
109 | 106 | |
|
110 | def test_filter_journal_filter_wildcard_on_repository(self): | |
|
111 | self.log_user() | |
|
107 | def test_filter_journal_filter_wildcard_on_repository(self, autologin_user): | |
|
112 | 108 | response = self.app.get(route_path('admin_audit_logs', |
|
113 | 109 | params=dict(filter='repository:*test*'))) |
|
114 | 110 | response.mustcontain('862 entries') |
|
115 | 111 | |
|
116 | def test_filter_journal_filter_prefix_on_repository(self): | |
|
117 | self.log_user() | |
|
112 | def test_filter_journal_filter_prefix_on_repository(self, autologin_user): | |
|
118 | 113 | response = self.app.get(route_path('admin_audit_logs', |
|
119 | 114 | params=dict(filter='repository:test*'))) |
|
120 | 115 | response.mustcontain('257 entries') |
|
121 | 116 | |
|
122 | def test_filter_journal_filter_prefix_on_repository_CamelCase(self): | |
|
123 | self.log_user() | |
|
117 | def test_filter_journal_filter_prefix_on_repository_CamelCase(self, autologin_user): | |
|
124 | 118 | response = self.app.get(route_path('admin_audit_logs', |
|
125 | 119 | params=dict(filter='repository:Test*'))) |
|
126 | 120 | response.mustcontain('257 entries') |
|
127 | 121 | |
|
128 | def test_filter_journal_filter_prefix_on_repository_and_user(self): | |
|
129 | self.log_user() | |
|
122 | def test_filter_journal_filter_prefix_on_repository_and_user(self, autologin_user): | |
|
130 | 123 | response = self.app.get(route_path('admin_audit_logs', |
|
131 | 124 | params=dict(filter='repository:test* AND username:demo'))) |
|
132 | 125 | response.mustcontain('130 entries') |
|
133 | 126 | |
|
134 | def test_filter_journal_filter_prefix_on_repository_or_target_repo(self): | |
|
135 | self.log_user() | |
|
127 | def test_filter_journal_filter_prefix_on_repository_or_target_repo(self, autologin_user): | |
|
136 | 128 | response = self.app.get(route_path('admin_audit_logs', |
|
137 | 129 | params=dict(filter='repository:test* OR repository:rhodecode'))) |
|
138 | 130 | response.mustcontain('260 entries') # 257 + 3 |
|
139 | 131 | |
|
140 | def test_filter_journal_filter_exact_match_on_username(self): | |
|
141 | self.log_user() | |
|
132 | def test_filter_journal_filter_exact_match_on_username(self, autologin_user): | |
|
142 | 133 | response = self.app.get(route_path('admin_audit_logs', |
|
143 | 134 | params=dict(filter='username:demo'))) |
|
144 | 135 | response.mustcontain('1087 entries') |
|
145 | 136 | |
|
146 | def test_filter_journal_filter_exact_match_on_username_camelCase(self): | |
|
147 | self.log_user() | |
|
137 | def test_filter_journal_filter_exact_match_on_username_camelCase(self, autologin_user): | |
|
148 | 138 | response = self.app.get(route_path('admin_audit_logs', |
|
149 | 139 | params=dict(filter='username:DemO'))) |
|
150 | 140 | response.mustcontain('1087 entries') |
|
151 | 141 | |
|
152 | def test_filter_journal_filter_wildcard_on_username(self): | |
|
153 | self.log_user() | |
|
142 | def test_filter_journal_filter_wildcard_on_username(self, autologin_user): | |
|
154 | 143 | response = self.app.get(route_path('admin_audit_logs', |
|
155 | 144 | params=dict(filter='username:*test*'))) |
|
156 | 145 | entries_count = UserLog.query().filter(UserLog.username.ilike('%test%')).count() |
|
157 | 146 | response.mustcontain('{} entries'.format(entries_count)) |
|
158 | 147 | |
|
159 | def test_filter_journal_filter_prefix_on_username(self): | |
|
160 | self.log_user() | |
|
148 | def test_filter_journal_filter_prefix_on_username(self, autologin_user): | |
|
161 | 149 | response = self.app.get(route_path('admin_audit_logs', |
|
162 | 150 | params=dict(filter='username:demo*'))) |
|
163 | 151 | response.mustcontain('1101 entries') |
|
164 | 152 | |
|
165 | def test_filter_journal_filter_prefix_on_user_or_other_user(self): | |
|
166 | self.log_user() | |
|
153 | def test_filter_journal_filter_prefix_on_user_or_other_user(self, autologin_user): | |
|
167 | 154 | response = self.app.get(route_path('admin_audit_logs', |
|
168 | 155 | params=dict(filter='username:demo OR username:volcan'))) |
|
169 | 156 | response.mustcontain('1095 entries') # 1087 + 8 |
|
170 | 157 | |
|
171 | def test_filter_journal_filter_wildcard_on_action(self): | |
|
172 | self.log_user() | |
|
158 | def test_filter_journal_filter_wildcard_on_action(self, autologin_user): | |
|
173 | 159 | response = self.app.get(route_path('admin_audit_logs', |
|
174 | 160 | params=dict(filter='action:*pull_request*'))) |
|
175 | 161 | response.mustcontain('187 entries') |
|
176 | 162 | |
|
177 | def test_filter_journal_filter_on_date(self): | |
|
178 | self.log_user() | |
|
163 | def test_filter_journal_filter_on_date(self, autologin_user): | |
|
179 | 164 | response = self.app.get(route_path('admin_audit_logs', |
|
180 | 165 | params=dict(filter='date:20121010'))) |
|
181 | 166 | response.mustcontain('47 entries') |
|
182 | 167 | |
|
183 | def test_filter_journal_filter_on_date_2(self): | |
|
184 | self.log_user() | |
|
168 | def test_filter_journal_filter_on_date_2(self, autologin_user): | |
|
185 | 169 | response = self.app.get(route_path('admin_audit_logs', |
|
186 | 170 | params=dict(filter='date:20121020'))) |
|
187 | 171 | response.mustcontain('17 entries') |
@@ -1,730 +1,730 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | |
|
3 | 3 | # Copyright (C) 2010-2017 RhodeCode GmbH |
|
4 | 4 | # |
|
5 | 5 | # This program is free software: you can redistribute it and/or modify |
|
6 | 6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
7 | 7 | # (only), as published by the Free Software Foundation. |
|
8 | 8 | # |
|
9 | 9 | # This program is distributed in the hope that it will be useful, |
|
10 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 | 12 | # GNU General Public License for more details. |
|
13 | 13 | # |
|
14 | 14 | # You should have received a copy of the GNU Affero General Public License |
|
15 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | 16 | # |
|
17 | 17 | # This program is dual-licensed. If you wish to learn more about the |
|
18 | 18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
19 | 19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
20 | 20 | |
|
21 | 21 | import mock |
|
22 | 22 | import pytest |
|
23 | 23 | |
|
24 | 24 | import rhodecode |
|
25 | 25 | from rhodecode.apps._base import ADMIN_PREFIX |
|
26 | 26 | from rhodecode.lib.utils2 import md5 |
|
27 | 27 | from rhodecode.model.db import RhodeCodeUi |
|
28 | 28 | from rhodecode.model.meta import Session |
|
29 | 29 | from rhodecode.model.settings import SettingsModel, IssueTrackerSettingsModel |
|
30 | 30 | from rhodecode.tests import assert_session_flash |
|
31 | 31 | from rhodecode.tests.utils import AssertResponse |
|
32 | 32 | |
|
33 | 33 | |
|
34 | 34 | UPDATE_DATA_QUALNAME = ( |
|
35 | 35 | 'rhodecode.apps.admin.views.system_info.AdminSystemInfoSettingsView.get_update_data') |
|
36 | 36 | |
|
37 | 37 | |
|
38 | 38 | def route_path(name, params=None, **kwargs): |
|
39 | 39 | import urllib |
|
40 | 40 | from rhodecode.apps._base import ADMIN_PREFIX |
|
41 | 41 | |
|
42 | 42 | base_url = { |
|
43 | 43 | |
|
44 | 44 | 'admin_settings': |
|
45 | 45 | ADMIN_PREFIX +'/settings', |
|
46 | 46 | 'admin_settings_update': |
|
47 | 47 | ADMIN_PREFIX + '/settings/update', |
|
48 | 48 | 'admin_settings_global': |
|
49 | 49 | ADMIN_PREFIX + '/settings/global', |
|
50 | 50 | 'admin_settings_global_update': |
|
51 | 51 | ADMIN_PREFIX + '/settings/global/update', |
|
52 | 52 | 'admin_settings_vcs': |
|
53 | 53 | ADMIN_PREFIX + '/settings/vcs', |
|
54 | 54 | 'admin_settings_vcs_update': |
|
55 | 55 | ADMIN_PREFIX + '/settings/vcs/update', |
|
56 | 56 | 'admin_settings_vcs_svn_pattern_delete': |
|
57 | 57 | ADMIN_PREFIX + '/settings/vcs/svn_pattern_delete', |
|
58 | 58 | 'admin_settings_mapping': |
|
59 | 59 | ADMIN_PREFIX + '/settings/mapping', |
|
60 | 60 | 'admin_settings_mapping_update': |
|
61 | 61 | ADMIN_PREFIX + '/settings/mapping/update', |
|
62 | 62 | 'admin_settings_visual': |
|
63 | 63 | ADMIN_PREFIX + '/settings/visual', |
|
64 | 64 | 'admin_settings_visual_update': |
|
65 | 65 | ADMIN_PREFIX + '/settings/visual/update', |
|
66 | 66 | 'admin_settings_issuetracker': |
|
67 | 67 | ADMIN_PREFIX + '/settings/issue-tracker', |
|
68 | 68 | 'admin_settings_issuetracker_update': |
|
69 | 69 | ADMIN_PREFIX + '/settings/issue-tracker/update', |
|
70 | 70 | 'admin_settings_issuetracker_test': |
|
71 | 71 | ADMIN_PREFIX + '/settings/issue-tracker/test', |
|
72 | 72 | 'admin_settings_issuetracker_delete': |
|
73 | 73 | ADMIN_PREFIX + '/settings/issue-tracker/delete', |
|
74 | 74 | 'admin_settings_email': |
|
75 | 75 | ADMIN_PREFIX + '/settings/email', |
|
76 | 76 | 'admin_settings_email_update': |
|
77 | 77 | ADMIN_PREFIX + '/settings/email/update', |
|
78 | 78 | 'admin_settings_hooks': |
|
79 | 79 | ADMIN_PREFIX + '/settings/hooks', |
|
80 | 80 | 'admin_settings_hooks_update': |
|
81 | 81 | ADMIN_PREFIX + '/settings/hooks/update', |
|
82 | 82 | 'admin_settings_hooks_delete': |
|
83 | 83 | ADMIN_PREFIX + '/settings/hooks/delete', |
|
84 | 84 | 'admin_settings_search': |
|
85 | 85 | ADMIN_PREFIX + '/settings/search', |
|
86 | 86 | 'admin_settings_labs': |
|
87 | 87 | ADMIN_PREFIX + '/settings/labs', |
|
88 | 88 | 'admin_settings_labs_update': |
|
89 | 89 | ADMIN_PREFIX + '/settings/labs/update', |
|
90 | 90 | |
|
91 | 91 | 'admin_settings_sessions': |
|
92 | 92 | ADMIN_PREFIX + '/settings/sessions', |
|
93 | 93 | 'admin_settings_sessions_cleanup': |
|
94 | 94 | ADMIN_PREFIX + '/settings/sessions/cleanup', |
|
95 | 95 | 'admin_settings_system': |
|
96 | 96 | ADMIN_PREFIX + '/settings/system', |
|
97 | 97 | 'admin_settings_system_update': |
|
98 | 98 | ADMIN_PREFIX + '/settings/system/updates', |
|
99 | 99 | 'admin_settings_open_source': |
|
100 | 100 | ADMIN_PREFIX + '/settings/open_source', |
|
101 | 101 | |
|
102 | 102 | |
|
103 | 103 | }[name].format(**kwargs) |
|
104 | 104 | |
|
105 | 105 | if params: |
|
106 | 106 | base_url = '{}?{}'.format(base_url, urllib.urlencode(params)) |
|
107 | 107 | return base_url |
|
108 | 108 | |
|
109 | 109 | |
|
110 | 110 | @pytest.mark.usefixtures('autologin_user', 'app') |
|
111 | 111 | class TestAdminSettingsController(object): |
|
112 | 112 | |
|
113 | 113 | @pytest.mark.parametrize('urlname', [ |
|
114 | 114 | 'admin_settings_vcs', |
|
115 | 115 | 'admin_settings_mapping', |
|
116 | 116 | 'admin_settings_global', |
|
117 | 117 | 'admin_settings_visual', |
|
118 | 118 | 'admin_settings_email', |
|
119 | 119 | 'admin_settings_hooks', |
|
120 | 120 | 'admin_settings_search', |
|
121 | 121 | ]) |
|
122 | 122 | def test_simple_get(self, urlname): |
|
123 | 123 | self.app.get(route_path(urlname)) |
|
124 | 124 | |
|
125 | 125 | def test_create_custom_hook(self, csrf_token): |
|
126 | 126 | response = self.app.post( |
|
127 | 127 | route_path('admin_settings_hooks_update'), |
|
128 | 128 | params={ |
|
129 | 129 | 'new_hook_ui_key': 'test_hooks_1', |
|
130 | 130 | 'new_hook_ui_value': 'cd /tmp', |
|
131 | 131 | 'csrf_token': csrf_token}) |
|
132 | 132 | |
|
133 | 133 | response = response.follow() |
|
134 | 134 | response.mustcontain('test_hooks_1') |
|
135 | 135 | response.mustcontain('cd /tmp') |
|
136 | 136 | |
|
137 | 137 | def test_create_custom_hook_delete(self, csrf_token): |
|
138 | 138 | response = self.app.post( |
|
139 | 139 | route_path('admin_settings_hooks_update'), |
|
140 | 140 | params={ |
|
141 | 141 | 'new_hook_ui_key': 'test_hooks_2', |
|
142 | 142 | 'new_hook_ui_value': 'cd /tmp2', |
|
143 | 143 | 'csrf_token': csrf_token}) |
|
144 | 144 | |
|
145 | 145 | response = response.follow() |
|
146 | 146 | response.mustcontain('test_hooks_2') |
|
147 | 147 | response.mustcontain('cd /tmp2') |
|
148 | 148 | |
|
149 | 149 | hook_id = SettingsModel().get_ui_by_key('test_hooks_2').ui_id |
|
150 | 150 | |
|
151 | 151 | # delete |
|
152 | 152 | self.app.post( |
|
153 | 153 | route_path('admin_settings_hooks_delete'), |
|
154 | 154 | params={'hook_id': hook_id, 'csrf_token': csrf_token}) |
|
155 | 155 | response = self.app.get(route_path('admin_settings_hooks')) |
|
156 | 156 | response.mustcontain(no=['test_hooks_2']) |
|
157 | 157 | response.mustcontain(no=['cd /tmp2']) |
|
158 | 158 | |
|
159 | 159 | |
|
160 | 160 | @pytest.mark.usefixtures('autologin_user', 'app') |
|
161 | 161 | class TestAdminSettingsGlobal(object): |
|
162 | 162 | |
|
163 | 163 | def test_pre_post_code_code_active(self, csrf_token): |
|
164 | 164 | pre_code = 'rc-pre-code-187652122' |
|
165 | 165 | post_code = 'rc-postcode-98165231' |
|
166 | 166 | |
|
167 | 167 | response = self.post_and_verify_settings({ |
|
168 | 168 | 'rhodecode_pre_code': pre_code, |
|
169 | 169 | 'rhodecode_post_code': post_code, |
|
170 | 170 | 'csrf_token': csrf_token, |
|
171 | 171 | }) |
|
172 | 172 | |
|
173 | 173 | response = response.follow() |
|
174 | 174 | response.mustcontain(pre_code, post_code) |
|
175 | 175 | |
|
176 | 176 | def test_pre_post_code_code_inactive(self, csrf_token): |
|
177 | 177 | pre_code = 'rc-pre-code-187652122' |
|
178 | 178 | post_code = 'rc-postcode-98165231' |
|
179 | 179 | response = self.post_and_verify_settings({ |
|
180 | 180 | 'rhodecode_pre_code': '', |
|
181 | 181 | 'rhodecode_post_code': '', |
|
182 | 182 | 'csrf_token': csrf_token, |
|
183 | 183 | }) |
|
184 | 184 | |
|
185 | 185 | response = response.follow() |
|
186 | 186 | response.mustcontain(no=[pre_code, post_code]) |
|
187 | 187 | |
|
188 | 188 | def test_captcha_activate(self, csrf_token): |
|
189 | 189 | self.post_and_verify_settings({ |
|
190 | 190 | 'rhodecode_captcha_private_key': '1234567890', |
|
191 | 191 | 'rhodecode_captcha_public_key': '1234567890', |
|
192 | 192 | 'csrf_token': csrf_token, |
|
193 | 193 | }) |
|
194 | 194 | |
|
195 | 195 | response = self.app.get(ADMIN_PREFIX + '/register') |
|
196 | 196 | response.mustcontain('captcha') |
|
197 | 197 | |
|
198 | 198 | def test_captcha_deactivate(self, csrf_token): |
|
199 | 199 | self.post_and_verify_settings({ |
|
200 | 200 | 'rhodecode_captcha_private_key': '', |
|
201 | 201 | 'rhodecode_captcha_public_key': '1234567890', |
|
202 | 202 | 'csrf_token': csrf_token, |
|
203 | 203 | }) |
|
204 | 204 | |
|
205 | 205 | response = self.app.get(ADMIN_PREFIX + '/register') |
|
206 | 206 | response.mustcontain(no=['captcha']) |
|
207 | 207 | |
|
208 | 208 | def test_title_change(self, csrf_token): |
|
209 | 209 | old_title = 'RhodeCode' |
|
210 | 210 | |
|
211 | 211 | for new_title in ['Changed', 'Żółwik', old_title]: |
|
212 | 212 | response = self.post_and_verify_settings({ |
|
213 | 213 | 'rhodecode_title': new_title, |
|
214 | 214 | 'csrf_token': csrf_token, |
|
215 | 215 | }) |
|
216 | 216 | |
|
217 | 217 | response = response.follow() |
|
218 | 218 | response.mustcontain( |
|
219 | 219 | """<div class="branding">- %s</div>""" % new_title) |
|
220 | 220 | |
|
221 | 221 | def post_and_verify_settings(self, settings): |
|
222 | 222 | old_title = 'RhodeCode' |
|
223 | 223 | old_realm = 'RhodeCode authentication' |
|
224 | 224 | params = { |
|
225 | 225 | 'rhodecode_title': old_title, |
|
226 | 226 | 'rhodecode_realm': old_realm, |
|
227 | 227 | 'rhodecode_pre_code': '', |
|
228 | 228 | 'rhodecode_post_code': '', |
|
229 | 229 | 'rhodecode_captcha_private_key': '', |
|
230 | 230 | 'rhodecode_captcha_public_key': '', |
|
231 | 231 | 'rhodecode_create_personal_repo_group': False, |
|
232 | 232 | 'rhodecode_personal_repo_group_pattern': '${username}', |
|
233 | 233 | } |
|
234 | 234 | params.update(settings) |
|
235 | 235 | response = self.app.post( |
|
236 | 236 | route_path('admin_settings_global_update'), params=params) |
|
237 | 237 | |
|
238 | 238 | assert_session_flash(response, 'Updated application settings') |
|
239 | 239 | app_settings = SettingsModel().get_all_settings() |
|
240 | 240 | del settings['csrf_token'] |
|
241 | 241 | for key, value in settings.iteritems(): |
|
242 | 242 | assert app_settings[key] == value.decode('utf-8') |
|
243 | 243 | |
|
244 | 244 | return response |
|
245 | 245 | |
|
246 | 246 | |
|
247 | 247 | @pytest.mark.usefixtures('autologin_user', 'app') |
|
248 | 248 | class TestAdminSettingsVcs(object): |
|
249 | 249 | |
|
250 | 250 | def test_contains_svn_default_patterns(self): |
|
251 | 251 | response = self.app.get(route_path('admin_settings_vcs')) |
|
252 | 252 | expected_patterns = [ |
|
253 | 253 | '/trunk', |
|
254 | 254 | '/branches/*', |
|
255 | 255 | '/tags/*', |
|
256 | 256 | ] |
|
257 | 257 | for pattern in expected_patterns: |
|
258 | 258 | response.mustcontain(pattern) |
|
259 | 259 | |
|
260 | 260 | def test_add_new_svn_branch_and_tag_pattern( |
|
261 | 261 | self, backend_svn, form_defaults, disable_sql_cache, |
|
262 | 262 | csrf_token): |
|
263 | 263 | form_defaults.update({ |
|
264 | 264 | 'new_svn_branch': '/exp/branches/*', |
|
265 | 265 | 'new_svn_tag': '/important_tags/*', |
|
266 | 266 | 'csrf_token': csrf_token, |
|
267 | 267 | }) |
|
268 | 268 | |
|
269 | 269 | response = self.app.post( |
|
270 | 270 | route_path('admin_settings_vcs_update'), |
|
271 | 271 | params=form_defaults, status=302) |
|
272 | 272 | response = response.follow() |
|
273 | 273 | |
|
274 | 274 | # Expect to find the new values on the page |
|
275 | 275 | response.mustcontain('/exp/branches/*') |
|
276 | 276 | response.mustcontain('/important_tags/*') |
|
277 | 277 | |
|
278 | 278 | # Expect that those patterns are used to match branches and tags now |
|
279 | 279 | repo = backend_svn['svn-simple-layout'].scm_instance() |
|
280 | 280 | assert 'exp/branches/exp-sphinx-docs' in repo.branches |
|
281 | 281 | assert 'important_tags/v0.5' in repo.tags |
|
282 | 282 | |
|
283 | 283 | def test_add_same_svn_value_twice_shows_an_error_message( |
|
284 | 284 | self, form_defaults, csrf_token, settings_util): |
|
285 | 285 | settings_util.create_rhodecode_ui('vcs_svn_branch', '/test') |
|
286 | 286 | settings_util.create_rhodecode_ui('vcs_svn_tag', '/test') |
|
287 | 287 | |
|
288 | 288 | response = self.app.post( |
|
289 | 289 | route_path('admin_settings_vcs_update'), |
|
290 | 290 | params={ |
|
291 | 291 | 'paths_root_path': form_defaults['paths_root_path'], |
|
292 | 292 | 'new_svn_branch': '/test', |
|
293 | 293 | 'new_svn_tag': '/test', |
|
294 | 294 | 'csrf_token': csrf_token, |
|
295 | 295 | }, |
|
296 | 296 | status=200) |
|
297 | 297 | |
|
298 | 298 | response.mustcontain("Pattern already exists") |
|
299 | 299 | response.mustcontain("Some form inputs contain invalid data.") |
|
300 | 300 | |
|
301 | 301 | @pytest.mark.parametrize('section', [ |
|
302 | 302 | 'vcs_svn_branch', |
|
303 | 303 | 'vcs_svn_tag', |
|
304 | 304 | ]) |
|
305 | 305 | def test_delete_svn_patterns( |
|
306 | 306 | self, section, csrf_token, settings_util): |
|
307 | 307 | setting = settings_util.create_rhodecode_ui( |
|
308 | 308 | section, '/test_delete', cleanup=False) |
|
309 | 309 | |
|
310 | 310 | self.app.post( |
|
311 | 311 | route_path('admin_settings_vcs_svn_pattern_delete'), |
|
312 | 312 | params={ |
|
313 | 313 | 'delete_svn_pattern': setting.ui_id, |
|
314 | 314 | 'csrf_token': csrf_token}, |
|
315 | 315 | headers={'X-REQUESTED-WITH': 'XMLHttpRequest'}) |
|
316 | 316 | |
|
317 | 317 | @pytest.mark.parametrize('section', [ |
|
318 | 318 | 'vcs_svn_branch', |
|
319 | 319 | 'vcs_svn_tag', |
|
320 | 320 | ]) |
|
321 | 321 | def test_delete_svn_patterns_raises_404_when_no_xhr( |
|
322 | 322 | self, section, csrf_token, settings_util): |
|
323 | 323 | setting = settings_util.create_rhodecode_ui(section, '/test_delete') |
|
324 | 324 | |
|
325 | 325 | self.app.post( |
|
326 | 326 | route_path('admin_settings_vcs_svn_pattern_delete'), |
|
327 | 327 | params={ |
|
328 | 328 | 'delete_svn_pattern': setting.ui_id, |
|
329 | 329 | 'csrf_token': csrf_token}, |
|
330 | 330 | status=404) |
|
331 | 331 | |
|
332 | 332 | def test_extensions_hgsubversion(self, form_defaults, csrf_token): |
|
333 | 333 | form_defaults.update({ |
|
334 | 334 | 'csrf_token': csrf_token, |
|
335 | 335 | 'extensions_hgsubversion': 'True', |
|
336 | 336 | }) |
|
337 | 337 | response = self.app.post( |
|
338 | 338 | route_path('admin_settings_vcs_update'), |
|
339 | 339 | params=form_defaults, |
|
340 | 340 | status=302) |
|
341 | 341 | |
|
342 | 342 | response = response.follow() |
|
343 | 343 | extensions_input = ( |
|
344 | 344 | '<input id="extensions_hgsubversion" ' |
|
345 | 345 | 'name="extensions_hgsubversion" type="checkbox" ' |
|
346 | 346 | 'value="True" checked="checked" />') |
|
347 | 347 | response.mustcontain(extensions_input) |
|
348 | 348 | |
|
349 | 349 | def test_extensions_hgevolve(self, form_defaults, csrf_token): |
|
350 | 350 | form_defaults.update({ |
|
351 | 351 | 'csrf_token': csrf_token, |
|
352 | 352 | 'extensions_evolve': 'True', |
|
353 | 353 | }) |
|
354 | 354 | response = self.app.post( |
|
355 | 355 | route_path('admin_settings_vcs_update'), |
|
356 | 356 | params=form_defaults, |
|
357 | 357 | status=302) |
|
358 | 358 | |
|
359 | 359 | response = response.follow() |
|
360 | 360 | extensions_input = ( |
|
361 | 361 | '<input id="extensions_evolve" ' |
|
362 | 362 | 'name="extensions_evolve" type="checkbox" ' |
|
363 | 363 | 'value="True" checked="checked" />') |
|
364 | 364 | response.mustcontain(extensions_input) |
|
365 | 365 | |
|
366 | 366 | def test_has_a_section_for_pull_request_settings(self): |
|
367 | 367 | response = self.app.get(route_path('admin_settings_vcs')) |
|
368 | 368 | response.mustcontain('Pull Request Settings') |
|
369 | 369 | |
|
370 | 370 | def test_has_an_input_for_invalidation_of_inline_comments(self): |
|
371 | 371 | response = self.app.get(route_path('admin_settings_vcs')) |
|
372 | 372 | assert_response = AssertResponse(response) |
|
373 | 373 | assert_response.one_element_exists( |
|
374 | 374 | '[name=rhodecode_use_outdated_comments]') |
|
375 | 375 | |
|
376 | 376 | @pytest.mark.parametrize('new_value', [True, False]) |
|
377 | 377 | def test_allows_to_change_invalidation_of_inline_comments( |
|
378 | 378 | self, form_defaults, csrf_token, new_value): |
|
379 | 379 | setting_key = 'use_outdated_comments' |
|
380 | 380 | setting = SettingsModel().create_or_update_setting( |
|
381 | 381 | setting_key, not new_value, 'bool') |
|
382 | 382 | Session().add(setting) |
|
383 | 383 | Session().commit() |
|
384 | 384 | |
|
385 | 385 | form_defaults.update({ |
|
386 | 386 | 'csrf_token': csrf_token, |
|
387 | 387 | 'rhodecode_use_outdated_comments': str(new_value), |
|
388 | 388 | }) |
|
389 | 389 | response = self.app.post( |
|
390 | 390 | route_path('admin_settings_vcs_update'), |
|
391 | 391 | params=form_defaults, |
|
392 | 392 | status=302) |
|
393 | 393 | response = response.follow() |
|
394 | 394 | setting = SettingsModel().get_setting_by_name(setting_key) |
|
395 | 395 | assert setting.app_settings_value is new_value |
|
396 | 396 | |
|
397 | 397 | @pytest.mark.parametrize('new_value', [True, False]) |
|
398 | 398 | def test_allows_to_change_hg_rebase_merge_strategy( |
|
399 | 399 | self, form_defaults, csrf_token, new_value): |
|
400 | 400 | setting_key = 'hg_use_rebase_for_merging' |
|
401 | 401 | |
|
402 | 402 | form_defaults.update({ |
|
403 | 403 | 'csrf_token': csrf_token, |
|
404 | 404 | 'rhodecode_' + setting_key: str(new_value), |
|
405 | 405 | }) |
|
406 | 406 | |
|
407 | 407 | with mock.patch.dict( |
|
408 | 408 | rhodecode.CONFIG, {'labs_settings_active': 'true'}): |
|
409 | 409 | self.app.post( |
|
410 | 410 | route_path('admin_settings_vcs_update'), |
|
411 | 411 | params=form_defaults, |
|
412 | 412 | status=302) |
|
413 | 413 | |
|
414 | 414 | setting = SettingsModel().get_setting_by_name(setting_key) |
|
415 | 415 | assert setting.app_settings_value is new_value |
|
416 | 416 | |
|
417 | 417 | @pytest.fixture |
|
418 | 418 | def disable_sql_cache(self, request): |
|
419 | 419 | patcher = mock.patch( |
|
420 | 420 | 'rhodecode.lib.caching_query.FromCache.process_query') |
|
421 | 421 | request.addfinalizer(patcher.stop) |
|
422 | 422 | patcher.start() |
|
423 | 423 | |
|
424 | 424 | @pytest.fixture |
|
425 | 425 | def form_defaults(self): |
|
426 | 426 | from rhodecode.apps.admin.views.settings import AdminSettingsView |
|
427 | 427 | return AdminSettingsView._form_defaults() |
|
428 | 428 | |
|
429 | 429 | # TODO: johbo: What we really want is to checkpoint before a test run and |
|
430 | 430 | # reset the session afterwards. |
|
431 | 431 | @pytest.fixture(scope='class', autouse=True) |
|
432 |
def cleanup_settings(self, request, |
|
|
432 | def cleanup_settings(self, request, baseapp): | |
|
433 | 433 | ui_id = RhodeCodeUi.ui_id |
|
434 | 434 | original_ids = list( |
|
435 | 435 | r.ui_id for r in RhodeCodeUi.query().values(ui_id)) |
|
436 | 436 | |
|
437 | 437 | @request.addfinalizer |
|
438 | 438 | def cleanup(): |
|
439 | 439 | RhodeCodeUi.query().filter( |
|
440 | 440 | ui_id.notin_(original_ids)).delete(False) |
|
441 | 441 | |
|
442 | 442 | |
|
443 | 443 | @pytest.mark.usefixtures('autologin_user', 'app') |
|
444 | 444 | class TestLabsSettings(object): |
|
445 | 445 | def test_get_settings_page_disabled(self): |
|
446 | 446 | with mock.patch.dict( |
|
447 | 447 | rhodecode.CONFIG, {'labs_settings_active': 'false'}): |
|
448 | 448 | |
|
449 | 449 | response = self.app.get( |
|
450 | 450 | route_path('admin_settings_labs'), status=302) |
|
451 | 451 | |
|
452 | 452 | assert response.location.endswith(route_path('admin_settings')) |
|
453 | 453 | |
|
454 | 454 | def test_get_settings_page_enabled(self): |
|
455 | 455 | from rhodecode.apps.admin.views import settings |
|
456 | 456 | lab_settings = [ |
|
457 | 457 | settings.LabSetting( |
|
458 | 458 | key='rhodecode_bool', |
|
459 | 459 | type='bool', |
|
460 | 460 | group='bool group', |
|
461 | 461 | label='bool label', |
|
462 | 462 | help='bool help' |
|
463 | 463 | ), |
|
464 | 464 | settings.LabSetting( |
|
465 | 465 | key='rhodecode_text', |
|
466 | 466 | type='unicode', |
|
467 | 467 | group='text group', |
|
468 | 468 | label='text label', |
|
469 | 469 | help='text help' |
|
470 | 470 | ), |
|
471 | 471 | ] |
|
472 | 472 | with mock.patch.dict(rhodecode.CONFIG, |
|
473 | 473 | {'labs_settings_active': 'true'}): |
|
474 | 474 | with mock.patch.object(settings, '_LAB_SETTINGS', lab_settings): |
|
475 | 475 | response = self.app.get(route_path('admin_settings_labs')) |
|
476 | 476 | |
|
477 | 477 | assert '<label>bool group:</label>' in response |
|
478 | 478 | assert '<label for="rhodecode_bool">bool label</label>' in response |
|
479 | 479 | assert '<p class="help-block">bool help</p>' in response |
|
480 | 480 | assert 'name="rhodecode_bool" type="checkbox"' in response |
|
481 | 481 | |
|
482 | 482 | assert '<label>text group:</label>' in response |
|
483 | 483 | assert '<label for="rhodecode_text">text label</label>' in response |
|
484 | 484 | assert '<p class="help-block">text help</p>' in response |
|
485 | 485 | assert 'name="rhodecode_text" size="60" type="text"' in response |
|
486 | 486 | |
|
487 | 487 | |
|
488 | 488 | @pytest.mark.usefixtures('app') |
|
489 | 489 | class TestOpenSourceLicenses(object): |
|
490 | 490 | |
|
491 | 491 | def test_records_are_displayed(self, autologin_user): |
|
492 | 492 | sample_licenses = { |
|
493 | 493 | "python2.7-pytest-2.7.1": { |
|
494 | 494 | "UNKNOWN": None |
|
495 | 495 | }, |
|
496 | 496 | "python2.7-Markdown-2.6.2": { |
|
497 | 497 | "BSD-3-Clause": "http://spdx.org/licenses/BSD-3-Clause" |
|
498 | 498 | } |
|
499 | 499 | } |
|
500 | 500 | read_licenses_patch = mock.patch( |
|
501 | 501 | 'rhodecode.apps.admin.views.open_source_licenses.read_opensource_licenses', |
|
502 | 502 | return_value=sample_licenses) |
|
503 | 503 | with read_licenses_patch: |
|
504 | 504 | response = self.app.get( |
|
505 | 505 | route_path('admin_settings_open_source'), status=200) |
|
506 | 506 | |
|
507 | 507 | assert_response = AssertResponse(response) |
|
508 | 508 | assert_response.element_contains( |
|
509 | 509 | '.panel-heading', 'Licenses of Third Party Packages') |
|
510 | 510 | for name in sample_licenses: |
|
511 | 511 | response.mustcontain(name) |
|
512 | 512 | for license in sample_licenses[name]: |
|
513 | 513 | assert_response.element_contains('.panel-body', license) |
|
514 | 514 | |
|
515 | 515 | def test_records_can_be_read(self, autologin_user): |
|
516 | 516 | response = self.app.get( |
|
517 | 517 | route_path('admin_settings_open_source'), status=200) |
|
518 | 518 | assert_response = AssertResponse(response) |
|
519 | 519 | assert_response.element_contains( |
|
520 | 520 | '.panel-heading', 'Licenses of Third Party Packages') |
|
521 | 521 | |
|
522 | 522 | def test_forbidden_when_normal_user(self, autologin_regular_user): |
|
523 | 523 | self.app.get( |
|
524 | 524 | route_path('admin_settings_open_source'), status=404) |
|
525 | 525 | |
|
526 | 526 | |
|
527 | 527 | @pytest.mark.usefixtures('app') |
|
528 | 528 | class TestUserSessions(object): |
|
529 | 529 | |
|
530 | 530 | def test_forbidden_when_normal_user(self, autologin_regular_user): |
|
531 | 531 | self.app.get(route_path('admin_settings_sessions'), status=404) |
|
532 | 532 | |
|
533 | 533 | def test_show_sessions_page(self, autologin_user): |
|
534 | 534 | response = self.app.get(route_path('admin_settings_sessions'), status=200) |
|
535 | 535 | response.mustcontain('file') |
|
536 | 536 | |
|
537 | 537 | def test_cleanup_old_sessions(self, autologin_user, csrf_token): |
|
538 | 538 | |
|
539 | 539 | post_data = { |
|
540 | 540 | 'csrf_token': csrf_token, |
|
541 | 541 | 'expire_days': '60' |
|
542 | 542 | } |
|
543 | 543 | response = self.app.post( |
|
544 | 544 | route_path('admin_settings_sessions_cleanup'), params=post_data, |
|
545 | 545 | status=302) |
|
546 | 546 | assert_session_flash(response, 'Cleaned up old sessions') |
|
547 | 547 | |
|
548 | 548 | |
|
549 | 549 | @pytest.mark.usefixtures('app') |
|
550 | 550 | class TestAdminSystemInfo(object): |
|
551 | 551 | |
|
552 | 552 | def test_forbidden_when_normal_user(self, autologin_regular_user): |
|
553 | 553 | self.app.get(route_path('admin_settings_system'), status=404) |
|
554 | 554 | |
|
555 | 555 | def test_system_info_page(self, autologin_user): |
|
556 | 556 | response = self.app.get(route_path('admin_settings_system')) |
|
557 | 557 | response.mustcontain('RhodeCode Community Edition, version {}'.format( |
|
558 | 558 | rhodecode.__version__)) |
|
559 | 559 | |
|
560 | 560 | def test_system_update_new_version(self, autologin_user): |
|
561 | 561 | update_data = { |
|
562 | 562 | 'versions': [ |
|
563 | 563 | { |
|
564 | 564 | 'version': '100.3.1415926535', |
|
565 | 565 | 'general': 'The latest version we are ever going to ship' |
|
566 | 566 | }, |
|
567 | 567 | { |
|
568 | 568 | 'version': '0.0.0', |
|
569 | 569 | 'general': 'The first version we ever shipped' |
|
570 | 570 | } |
|
571 | 571 | ] |
|
572 | 572 | } |
|
573 | 573 | with mock.patch(UPDATE_DATA_QUALNAME, return_value=update_data): |
|
574 | 574 | response = self.app.get(route_path('admin_settings_system_update')) |
|
575 | 575 | response.mustcontain('A <b>new version</b> is available') |
|
576 | 576 | |
|
577 | 577 | def test_system_update_nothing_new(self, autologin_user): |
|
578 | 578 | update_data = { |
|
579 | 579 | 'versions': [ |
|
580 | 580 | { |
|
581 | 581 | 'version': '0.0.0', |
|
582 | 582 | 'general': 'The first version we ever shipped' |
|
583 | 583 | } |
|
584 | 584 | ] |
|
585 | 585 | } |
|
586 | 586 | with mock.patch(UPDATE_DATA_QUALNAME, return_value=update_data): |
|
587 | 587 | response = self.app.get(route_path('admin_settings_system_update')) |
|
588 | 588 | response.mustcontain( |
|
589 | 589 | 'You already have the <b>latest</b> stable version.') |
|
590 | 590 | |
|
591 | 591 | def test_system_update_bad_response(self, autologin_user): |
|
592 | 592 | with mock.patch(UPDATE_DATA_QUALNAME, side_effect=ValueError('foo')): |
|
593 | 593 | response = self.app.get(route_path('admin_settings_system_update')) |
|
594 | 594 | response.mustcontain( |
|
595 | 595 | 'Bad data sent from update server') |
|
596 | 596 | |
|
597 | 597 | |
|
598 | 598 | @pytest.mark.usefixtures("app") |
|
599 | 599 | class TestAdminSettingsIssueTracker(object): |
|
600 | 600 | RC_PREFIX = 'rhodecode_' |
|
601 | 601 | SHORT_PATTERN_KEY = 'issuetracker_pat_' |
|
602 | 602 | PATTERN_KEY = RC_PREFIX + SHORT_PATTERN_KEY |
|
603 | 603 | |
|
604 | 604 | def test_issuetracker_index(self, autologin_user): |
|
605 | 605 | response = self.app.get(route_path('admin_settings_issuetracker')) |
|
606 | 606 | assert response.status_code == 200 |
|
607 | 607 | |
|
608 | 608 | def test_add_empty_issuetracker_pattern( |
|
609 | 609 | self, request, autologin_user, csrf_token): |
|
610 | 610 | post_url = route_path('admin_settings_issuetracker_update') |
|
611 | 611 | post_data = { |
|
612 | 612 | 'csrf_token': csrf_token |
|
613 | 613 | } |
|
614 | 614 | self.app.post(post_url, post_data, status=302) |
|
615 | 615 | |
|
616 | 616 | def test_add_issuetracker_pattern( |
|
617 | 617 | self, request, autologin_user, csrf_token): |
|
618 | 618 | pattern = 'issuetracker_pat' |
|
619 | 619 | another_pattern = pattern+'1' |
|
620 | 620 | post_url = route_path('admin_settings_issuetracker_update') |
|
621 | 621 | post_data = { |
|
622 | 622 | 'new_pattern_pattern_0': pattern, |
|
623 | 623 | 'new_pattern_url_0': 'http://url', |
|
624 | 624 | 'new_pattern_prefix_0': 'prefix', |
|
625 | 625 | 'new_pattern_description_0': 'description', |
|
626 | 626 | 'new_pattern_pattern_1': another_pattern, |
|
627 | 627 | 'new_pattern_url_1': 'https://url1', |
|
628 | 628 | 'new_pattern_prefix_1': 'prefix1', |
|
629 | 629 | 'new_pattern_description_1': 'description1', |
|
630 | 630 | 'csrf_token': csrf_token |
|
631 | 631 | } |
|
632 | 632 | self.app.post(post_url, post_data, status=302) |
|
633 | 633 | settings = SettingsModel().get_all_settings() |
|
634 | 634 | self.uid = md5(pattern) |
|
635 | 635 | assert settings[self.PATTERN_KEY+self.uid] == pattern |
|
636 | 636 | self.another_uid = md5(another_pattern) |
|
637 | 637 | assert settings[self.PATTERN_KEY+self.another_uid] == another_pattern |
|
638 | 638 | |
|
639 | 639 | @request.addfinalizer |
|
640 | 640 | def cleanup(): |
|
641 | 641 | defaults = SettingsModel().get_all_settings() |
|
642 | 642 | |
|
643 | 643 | entries = [name for name in defaults if ( |
|
644 | 644 | (self.uid in name) or (self.another_uid) in name)] |
|
645 | 645 | start = len(self.RC_PREFIX) |
|
646 | 646 | for del_key in entries: |
|
647 | 647 | # TODO: anderson: get_by_name needs name without prefix |
|
648 | 648 | entry = SettingsModel().get_setting_by_name(del_key[start:]) |
|
649 | 649 | Session().delete(entry) |
|
650 | 650 | |
|
651 | 651 | Session().commit() |
|
652 | 652 | |
|
653 | 653 | def test_edit_issuetracker_pattern( |
|
654 | 654 | self, autologin_user, backend, csrf_token, request): |
|
655 | 655 | old_pattern = 'issuetracker_pat' |
|
656 | 656 | old_uid = md5(old_pattern) |
|
657 | 657 | pattern = 'issuetracker_pat_new' |
|
658 | 658 | self.new_uid = md5(pattern) |
|
659 | 659 | |
|
660 | 660 | SettingsModel().create_or_update_setting( |
|
661 | 661 | self.SHORT_PATTERN_KEY+old_uid, old_pattern, 'unicode') |
|
662 | 662 | |
|
663 | 663 | post_url = route_path('admin_settings_issuetracker_update') |
|
664 | 664 | post_data = { |
|
665 | 665 | 'new_pattern_pattern_0': pattern, |
|
666 | 666 | 'new_pattern_url_0': 'https://url', |
|
667 | 667 | 'new_pattern_prefix_0': 'prefix', |
|
668 | 668 | 'new_pattern_description_0': 'description', |
|
669 | 669 | 'uid': old_uid, |
|
670 | 670 | 'csrf_token': csrf_token |
|
671 | 671 | } |
|
672 | 672 | self.app.post(post_url, post_data, status=302) |
|
673 | 673 | settings = SettingsModel().get_all_settings() |
|
674 | 674 | assert settings[self.PATTERN_KEY+self.new_uid] == pattern |
|
675 | 675 | assert self.PATTERN_KEY+old_uid not in settings |
|
676 | 676 | |
|
677 | 677 | @request.addfinalizer |
|
678 | 678 | def cleanup(): |
|
679 | 679 | IssueTrackerSettingsModel().delete_entries(self.new_uid) |
|
680 | 680 | |
|
681 | 681 | def test_replace_issuetracker_pattern_description( |
|
682 | 682 | self, autologin_user, csrf_token, request, settings_util): |
|
683 | 683 | prefix = 'issuetracker' |
|
684 | 684 | pattern = 'issuetracker_pat' |
|
685 | 685 | self.uid = md5(pattern) |
|
686 | 686 | pattern_key = '_'.join([prefix, 'pat', self.uid]) |
|
687 | 687 | rc_pattern_key = '_'.join(['rhodecode', pattern_key]) |
|
688 | 688 | desc_key = '_'.join([prefix, 'desc', self.uid]) |
|
689 | 689 | rc_desc_key = '_'.join(['rhodecode', desc_key]) |
|
690 | 690 | new_description = 'new_description' |
|
691 | 691 | |
|
692 | 692 | settings_util.create_rhodecode_setting( |
|
693 | 693 | pattern_key, pattern, 'unicode', cleanup=False) |
|
694 | 694 | settings_util.create_rhodecode_setting( |
|
695 | 695 | desc_key, 'old description', 'unicode', cleanup=False) |
|
696 | 696 | |
|
697 | 697 | post_url = route_path('admin_settings_issuetracker_update') |
|
698 | 698 | post_data = { |
|
699 | 699 | 'new_pattern_pattern_0': pattern, |
|
700 | 700 | 'new_pattern_url_0': 'https://url', |
|
701 | 701 | 'new_pattern_prefix_0': 'prefix', |
|
702 | 702 | 'new_pattern_description_0': new_description, |
|
703 | 703 | 'uid': self.uid, |
|
704 | 704 | 'csrf_token': csrf_token |
|
705 | 705 | } |
|
706 | 706 | self.app.post(post_url, post_data, status=302) |
|
707 | 707 | settings = SettingsModel().get_all_settings() |
|
708 | 708 | assert settings[rc_pattern_key] == pattern |
|
709 | 709 | assert settings[rc_desc_key] == new_description |
|
710 | 710 | |
|
711 | 711 | @request.addfinalizer |
|
712 | 712 | def cleanup(): |
|
713 | 713 | IssueTrackerSettingsModel().delete_entries(self.uid) |
|
714 | 714 | |
|
715 | 715 | def test_delete_issuetracker_pattern( |
|
716 | 716 | self, autologin_user, backend, csrf_token, settings_util): |
|
717 | 717 | pattern = 'issuetracker_pat' |
|
718 | 718 | uid = md5(pattern) |
|
719 | 719 | settings_util.create_rhodecode_setting( |
|
720 | 720 | self.SHORT_PATTERN_KEY+uid, pattern, 'unicode', cleanup=False) |
|
721 | 721 | |
|
722 | 722 | post_url = route_path('admin_settings_issuetracker_delete') |
|
723 | 723 | post_data = { |
|
724 | 724 | '_method': 'delete', |
|
725 | 725 | 'uid': uid, |
|
726 | 726 | 'csrf_token': csrf_token |
|
727 | 727 | } |
|
728 | 728 | self.app.post(post_url, post_data, status=302) |
|
729 | 729 | settings = SettingsModel().get_all_settings() |
|
730 | 730 | assert 'rhodecode_%s%s' % (self.SHORT_PATTERN_KEY, uid) not in settings |
@@ -1,783 +1,781 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | |
|
3 | 3 | # Copyright (C) 2010-2017 RhodeCode GmbH |
|
4 | 4 | # |
|
5 | 5 | # This program is free software: you can redistribute it and/or modify |
|
6 | 6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
7 | 7 | # (only), as published by the Free Software Foundation. |
|
8 | 8 | # |
|
9 | 9 | # This program is distributed in the hope that it will be useful, |
|
10 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 | 12 | # GNU General Public License for more details. |
|
13 | 13 | # |
|
14 | 14 | # You should have received a copy of the GNU Affero General Public License |
|
15 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | 16 | # |
|
17 | 17 | # This program is dual-licensed. If you wish to learn more about the |
|
18 | 18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
19 | 19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
20 | 20 | |
|
21 | 21 | import pytest |
|
22 | 22 | from sqlalchemy.orm.exc import NoResultFound |
|
23 | 23 | |
|
24 | 24 | from rhodecode.lib import auth |
|
25 | 25 | from rhodecode.lib import helpers as h |
|
26 | from rhodecode.model import validators | |
|
27 | 26 | from rhodecode.model.db import User, UserApiKeys, UserEmailMap, Repository |
|
28 | 27 | from rhodecode.model.meta import Session |
|
29 | 28 | from rhodecode.model.user import UserModel |
|
30 | 29 | |
|
31 | 30 | from rhodecode.tests import ( |
|
32 | 31 | TestController, TEST_USER_REGULAR_LOGIN, assert_session_flash) |
|
33 | 32 | from rhodecode.tests.fixture import Fixture |
|
34 | 33 | |
|
35 | 34 | fixture = Fixture() |
|
36 | 35 | |
|
37 | 36 | |
|
38 | 37 | def route_path(name, params=None, **kwargs): |
|
39 | 38 | import urllib |
|
40 | 39 | from rhodecode.apps._base import ADMIN_PREFIX |
|
41 | 40 | |
|
42 | 41 | base_url = { |
|
43 | 42 | 'users': |
|
44 | 43 | ADMIN_PREFIX + '/users', |
|
45 | 44 | 'users_data': |
|
46 | 45 | ADMIN_PREFIX + '/users_data', |
|
47 | 46 | 'users_create': |
|
48 | 47 | ADMIN_PREFIX + '/users/create', |
|
49 | 48 | 'users_new': |
|
50 | 49 | ADMIN_PREFIX + '/users/new', |
|
51 | 50 | 'user_edit': |
|
52 | 51 | ADMIN_PREFIX + '/users/{user_id}/edit', |
|
53 | 52 | 'user_edit_advanced': |
|
54 | 53 | ADMIN_PREFIX + '/users/{user_id}/edit/advanced', |
|
55 | 54 | 'user_edit_global_perms': |
|
56 | 55 | ADMIN_PREFIX + '/users/{user_id}/edit/global_permissions', |
|
57 | 56 | 'user_edit_global_perms_update': |
|
58 | 57 | ADMIN_PREFIX + '/users/{user_id}/edit/global_permissions/update', |
|
59 | 58 | 'user_update': |
|
60 | 59 | ADMIN_PREFIX + '/users/{user_id}/update', |
|
61 | 60 | 'user_delete': |
|
62 | 61 | ADMIN_PREFIX + '/users/{user_id}/delete', |
|
63 | 62 | 'user_force_password_reset': |
|
64 | 63 | ADMIN_PREFIX + '/users/{user_id}/password_reset', |
|
65 | 64 | 'user_create_personal_repo_group': |
|
66 | 65 | ADMIN_PREFIX + '/users/{user_id}/create_repo_group', |
|
67 | 66 | |
|
68 | 67 | 'edit_user_auth_tokens': |
|
69 | 68 | ADMIN_PREFIX + '/users/{user_id}/edit/auth_tokens', |
|
70 | 69 | 'edit_user_auth_tokens_add': |
|
71 | 70 | ADMIN_PREFIX + '/users/{user_id}/edit/auth_tokens/new', |
|
72 | 71 | 'edit_user_auth_tokens_delete': |
|
73 | 72 | ADMIN_PREFIX + '/users/{user_id}/edit/auth_tokens/delete', |
|
74 | 73 | |
|
75 | 74 | 'edit_user_emails': |
|
76 | 75 | ADMIN_PREFIX + '/users/{user_id}/edit/emails', |
|
77 | 76 | 'edit_user_emails_add': |
|
78 | 77 | ADMIN_PREFIX + '/users/{user_id}/edit/emails/new', |
|
79 | 78 | 'edit_user_emails_delete': |
|
80 | 79 | ADMIN_PREFIX + '/users/{user_id}/edit/emails/delete', |
|
81 | 80 | |
|
82 | 81 | 'edit_user_ips': |
|
83 | 82 | ADMIN_PREFIX + '/users/{user_id}/edit/ips', |
|
84 | 83 | 'edit_user_ips_add': |
|
85 | 84 | ADMIN_PREFIX + '/users/{user_id}/edit/ips/new', |
|
86 | 85 | 'edit_user_ips_delete': |
|
87 | 86 | ADMIN_PREFIX + '/users/{user_id}/edit/ips/delete', |
|
88 | 87 | |
|
89 | 88 | 'edit_user_perms_summary': |
|
90 | 89 | ADMIN_PREFIX + '/users/{user_id}/edit/permissions_summary', |
|
91 | 90 | 'edit_user_perms_summary_json': |
|
92 | 91 | ADMIN_PREFIX + '/users/{user_id}/edit/permissions_summary/json', |
|
93 | 92 | |
|
94 | 93 | 'edit_user_audit_logs': |
|
95 | 94 | ADMIN_PREFIX + '/users/{user_id}/edit/audit', |
|
96 | 95 | |
|
97 | 96 | }[name].format(**kwargs) |
|
98 | 97 | |
|
99 | 98 | if params: |
|
100 | 99 | base_url = '{}?{}'.format(base_url, urllib.urlencode(params)) |
|
101 | 100 | return base_url |
|
102 | 101 | |
|
103 | 102 | |
|
104 | 103 | class TestAdminUsersView(TestController): |
|
105 | 104 | |
|
106 | 105 | def test_show_users(self): |
|
107 | 106 | self.log_user() |
|
108 | 107 | self.app.get(route_path('users')) |
|
109 | 108 | |
|
110 | 109 | def test_show_users_data(self, xhr_header): |
|
111 | 110 | self.log_user() |
|
112 | 111 | response = self.app.get(route_path( |
|
113 | 112 | 'users_data'), extra_environ=xhr_header) |
|
114 | 113 | |
|
115 | 114 | all_users = User.query().filter( |
|
116 | 115 | User.username != User.DEFAULT_USER).count() |
|
117 | 116 | assert response.json['recordsTotal'] == all_users |
|
118 | 117 | |
|
119 | 118 | def test_show_users_data_filtered(self, xhr_header): |
|
120 | 119 | self.log_user() |
|
121 | 120 | response = self.app.get(route_path( |
|
122 | 121 | 'users_data', params={'search[value]': 'empty_search'}), |
|
123 | 122 | extra_environ=xhr_header) |
|
124 | 123 | |
|
125 | 124 | all_users = User.query().filter( |
|
126 | 125 | User.username != User.DEFAULT_USER).count() |
|
127 | 126 | assert response.json['recordsTotal'] == all_users |
|
128 | 127 | assert response.json['recordsFiltered'] == 0 |
|
129 | 128 | |
|
130 | 129 | def test_auth_tokens_default_user(self): |
|
131 | 130 | self.log_user() |
|
132 | 131 | user = User.get_default_user() |
|
133 | 132 | response = self.app.get( |
|
134 | 133 | route_path('edit_user_auth_tokens', user_id=user.user_id), |
|
135 | 134 | status=302) |
|
136 | 135 | |
|
137 | 136 | def test_auth_tokens(self): |
|
138 | 137 | self.log_user() |
|
139 | 138 | |
|
140 | 139 | user = User.get_by_username(TEST_USER_REGULAR_LOGIN) |
|
141 | 140 | response = self.app.get( |
|
142 | 141 | route_path('edit_user_auth_tokens', user_id=user.user_id)) |
|
143 | 142 | for token in user.auth_tokens: |
|
144 | 143 | response.mustcontain(token) |
|
145 | 144 | response.mustcontain('never') |
|
146 | 145 | |
|
147 | 146 | @pytest.mark.parametrize("desc, lifetime", [ |
|
148 | 147 | ('forever', -1), |
|
149 | 148 | ('5mins', 60*5), |
|
150 | 149 | ('30days', 60*60*24*30), |
|
151 | 150 | ]) |
|
152 | 151 | def test_add_auth_token(self, desc, lifetime, user_util): |
|
153 | 152 | self.log_user() |
|
154 | 153 | user = user_util.create_user() |
|
155 | 154 | user_id = user.user_id |
|
156 | 155 | |
|
157 | 156 | response = self.app.post( |
|
158 | 157 | route_path('edit_user_auth_tokens_add', user_id=user_id), |
|
159 | 158 | {'description': desc, 'lifetime': lifetime, |
|
160 | 159 | 'csrf_token': self.csrf_token}) |
|
161 | 160 | assert_session_flash(response, 'Auth token successfully created') |
|
162 | 161 | |
|
163 | 162 | response = response.follow() |
|
164 | 163 | user = User.get(user_id) |
|
165 | 164 | for auth_token in user.auth_tokens: |
|
166 | 165 | response.mustcontain(auth_token) |
|
167 | 166 | |
|
168 | 167 | def test_delete_auth_token(self, user_util): |
|
169 | 168 | self.log_user() |
|
170 | 169 | user = user_util.create_user() |
|
171 | 170 | user_id = user.user_id |
|
172 | 171 | keys = user.auth_tokens |
|
173 | 172 | assert 2 == len(keys) |
|
174 | 173 | |
|
175 | 174 | response = self.app.post( |
|
176 | 175 | route_path('edit_user_auth_tokens_add', user_id=user_id), |
|
177 | 176 | {'description': 'desc', 'lifetime': -1, |
|
178 | 177 | 'csrf_token': self.csrf_token}) |
|
179 | 178 | assert_session_flash(response, 'Auth token successfully created') |
|
180 | 179 | response.follow() |
|
181 | 180 | |
|
182 | 181 | # now delete our key |
|
183 | 182 | keys = UserApiKeys.query().filter(UserApiKeys.user_id == user_id).all() |
|
184 | 183 | assert 3 == len(keys) |
|
185 | 184 | |
|
186 | 185 | response = self.app.post( |
|
187 | 186 | route_path('edit_user_auth_tokens_delete', user_id=user_id), |
|
188 | 187 | {'del_auth_token': keys[0].user_api_key_id, |
|
189 | 188 | 'csrf_token': self.csrf_token}) |
|
190 | 189 | |
|
191 | 190 | assert_session_flash(response, 'Auth token successfully deleted') |
|
192 | 191 | keys = UserApiKeys.query().filter(UserApiKeys.user_id == user_id).all() |
|
193 | 192 | assert 2 == len(keys) |
|
194 | 193 | |
|
195 | 194 | def test_ips(self): |
|
196 | 195 | self.log_user() |
|
197 | 196 | user = User.get_by_username(TEST_USER_REGULAR_LOGIN) |
|
198 | 197 | response = self.app.get(route_path('edit_user_ips', user_id=user.user_id)) |
|
199 | 198 | response.mustcontain('All IP addresses are allowed') |
|
200 | 199 | |
|
201 | 200 | @pytest.mark.parametrize("test_name, ip, ip_range, failure", [ |
|
202 | 201 | ('127/24', '127.0.0.1/24', '127.0.0.0 - 127.0.0.255', False), |
|
203 | 202 | ('10/32', '10.0.0.10/32', '10.0.0.10 - 10.0.0.10', False), |
|
204 | 203 | ('0/16', '0.0.0.0/16', '0.0.0.0 - 0.0.255.255', False), |
|
205 | 204 | ('0/8', '0.0.0.0/8', '0.0.0.0 - 0.255.255.255', False), |
|
206 | 205 | ('127_bad_mask', '127.0.0.1/99', '127.0.0.1 - 127.0.0.1', True), |
|
207 | 206 | ('127_bad_ip', 'foobar', 'foobar', True), |
|
208 | 207 | ]) |
|
209 | 208 | def test_ips_add(self, user_util, test_name, ip, ip_range, failure): |
|
210 | 209 | self.log_user() |
|
211 | 210 | user = user_util.create_user(username=test_name) |
|
212 | 211 | user_id = user.user_id |
|
213 | 212 | |
|
214 | 213 | response = self.app.post( |
|
215 | 214 | route_path('edit_user_ips_add', user_id=user_id), |
|
216 | 215 | params={'new_ip': ip, 'csrf_token': self.csrf_token}) |
|
217 | 216 | |
|
218 | 217 | if failure: |
|
219 | 218 | assert_session_flash( |
|
220 | 219 | response, 'Please enter a valid IPv4 or IpV6 address') |
|
221 | 220 | response = self.app.get(route_path('edit_user_ips', user_id=user_id)) |
|
222 | 221 | |
|
223 | 222 | response.mustcontain(no=[ip]) |
|
224 | 223 | response.mustcontain(no=[ip_range]) |
|
225 | 224 | |
|
226 | 225 | else: |
|
227 | 226 | response = self.app.get(route_path('edit_user_ips', user_id=user_id)) |
|
228 | 227 | response.mustcontain(ip) |
|
229 | 228 | response.mustcontain(ip_range) |
|
230 | 229 | |
|
231 | 230 | def test_ips_delete(self, user_util): |
|
232 | 231 | self.log_user() |
|
233 | 232 | user = user_util.create_user() |
|
234 | 233 | user_id = user.user_id |
|
235 | 234 | ip = '127.0.0.1/32' |
|
236 | 235 | ip_range = '127.0.0.1 - 127.0.0.1' |
|
237 | 236 | new_ip = UserModel().add_extra_ip(user_id, ip) |
|
238 | 237 | Session().commit() |
|
239 | 238 | new_ip_id = new_ip.ip_id |
|
240 | 239 | |
|
241 | 240 | response = self.app.get(route_path('edit_user_ips', user_id=user_id)) |
|
242 | 241 | response.mustcontain(ip) |
|
243 | 242 | response.mustcontain(ip_range) |
|
244 | 243 | |
|
245 | 244 | self.app.post( |
|
246 | 245 | route_path('edit_user_ips_delete', user_id=user_id), |
|
247 | 246 | params={'del_ip_id': new_ip_id, 'csrf_token': self.csrf_token}) |
|
248 | 247 | |
|
249 | 248 | response = self.app.get(route_path('edit_user_ips', user_id=user_id)) |
|
250 | 249 | response.mustcontain('All IP addresses are allowed') |
|
251 | 250 | response.mustcontain(no=[ip]) |
|
252 | 251 | response.mustcontain(no=[ip_range]) |
|
253 | 252 | |
|
254 | 253 | def test_emails(self): |
|
255 | 254 | self.log_user() |
|
256 | 255 | user = User.get_by_username(TEST_USER_REGULAR_LOGIN) |
|
257 | 256 | response = self.app.get( |
|
258 | 257 | route_path('edit_user_emails', user_id=user.user_id)) |
|
259 | 258 | response.mustcontain('No additional emails specified') |
|
260 | 259 | |
|
261 | 260 | def test_emails_add(self, user_util): |
|
262 | 261 | self.log_user() |
|
263 | 262 | user = user_util.create_user() |
|
264 | 263 | user_id = user.user_id |
|
265 | 264 | |
|
266 | 265 | self.app.post( |
|
267 | 266 | route_path('edit_user_emails_add', user_id=user_id), |
|
268 | 267 | params={'new_email': 'example@rhodecode.com', |
|
269 | 268 | 'csrf_token': self.csrf_token}) |
|
270 | 269 | |
|
271 | 270 | response = self.app.get( |
|
272 | 271 | route_path('edit_user_emails', user_id=user_id)) |
|
273 | 272 | response.mustcontain('example@rhodecode.com') |
|
274 | 273 | |
|
275 | 274 | def test_emails_add_existing_email(self, user_util, user_regular): |
|
276 | 275 | existing_email = user_regular.email |
|
277 | 276 | |
|
278 | 277 | self.log_user() |
|
279 | 278 | user = user_util.create_user() |
|
280 | 279 | user_id = user.user_id |
|
281 | 280 | |
|
282 | 281 | response = self.app.post( |
|
283 | 282 | route_path('edit_user_emails_add', user_id=user_id), |
|
284 | 283 | params={'new_email': existing_email, |
|
285 | 284 | 'csrf_token': self.csrf_token}) |
|
286 | 285 | assert_session_flash( |
|
287 | 286 | response, 'This e-mail address is already taken') |
|
288 | 287 | |
|
289 | 288 | response = self.app.get( |
|
290 | 289 | route_path('edit_user_emails', user_id=user_id)) |
|
291 | 290 | response.mustcontain(no=[existing_email]) |
|
292 | 291 | |
|
293 | 292 | def test_emails_delete(self, user_util): |
|
294 | 293 | self.log_user() |
|
295 | 294 | user = user_util.create_user() |
|
296 | 295 | user_id = user.user_id |
|
297 | 296 | |
|
298 | 297 | self.app.post( |
|
299 | 298 | route_path('edit_user_emails_add', user_id=user_id), |
|
300 | 299 | params={'new_email': 'example@rhodecode.com', |
|
301 | 300 | 'csrf_token': self.csrf_token}) |
|
302 | 301 | |
|
303 | 302 | response = self.app.get( |
|
304 | 303 | route_path('edit_user_emails', user_id=user_id)) |
|
305 | 304 | response.mustcontain('example@rhodecode.com') |
|
306 | 305 | |
|
307 | 306 | user_email = UserEmailMap.query()\ |
|
308 | 307 | .filter(UserEmailMap.email == 'example@rhodecode.com') \ |
|
309 | 308 | .filter(UserEmailMap.user_id == user_id)\ |
|
310 | 309 | .one() |
|
311 | 310 | |
|
312 | 311 | del_email_id = user_email.email_id |
|
313 | 312 | self.app.post( |
|
314 | 313 | route_path('edit_user_emails_delete', user_id=user_id), |
|
315 | 314 | params={'del_email_id': del_email_id, |
|
316 | 315 | 'csrf_token': self.csrf_token}) |
|
317 | 316 | |
|
318 | 317 | response = self.app.get( |
|
319 | 318 | route_path('edit_user_emails', user_id=user_id)) |
|
320 | 319 | response.mustcontain(no=['example@rhodecode.com']) |
|
321 | 320 | |
|
322 | 321 | |
|
323 | 322 | def test_create(self, request, xhr_header): |
|
324 | 323 | self.log_user() |
|
325 | 324 | username = 'newtestuser' |
|
326 | 325 | password = 'test12' |
|
327 | 326 | password_confirmation = password |
|
328 | 327 | name = 'name' |
|
329 | 328 | lastname = 'lastname' |
|
330 | 329 | email = 'mail@mail.com' |
|
331 | 330 | |
|
332 | 331 | self.app.get(route_path('users_new')) |
|
333 | 332 | |
|
334 | 333 | response = self.app.post(route_path('users_create'), params={ |
|
335 | 334 | 'username': username, |
|
336 | 335 | 'password': password, |
|
337 | 336 | 'password_confirmation': password_confirmation, |
|
338 | 337 | 'firstname': name, |
|
339 | 338 | 'active': True, |
|
340 | 339 | 'lastname': lastname, |
|
341 | 340 | 'extern_name': 'rhodecode', |
|
342 | 341 | 'extern_type': 'rhodecode', |
|
343 | 342 | 'email': email, |
|
344 | 343 | 'csrf_token': self.csrf_token, |
|
345 | 344 | }) |
|
346 | 345 | user_link = h.link_to( |
|
347 | 346 | username, |
|
348 | 347 | route_path( |
|
349 | 348 | 'user_edit', user_id=User.get_by_username(username).user_id)) |
|
350 | 349 | assert_session_flash(response, 'Created user %s' % (user_link,)) |
|
351 | 350 | |
|
352 | 351 | @request.addfinalizer |
|
353 | 352 | def cleanup(): |
|
354 | 353 | fixture.destroy_user(username) |
|
355 | 354 | Session().commit() |
|
356 | 355 | |
|
357 | 356 | new_user = User.query().filter(User.username == username).one() |
|
358 | 357 | |
|
359 | 358 | assert new_user.username == username |
|
360 | 359 | assert auth.check_password(password, new_user.password) |
|
361 | 360 | assert new_user.name == name |
|
362 | 361 | assert new_user.lastname == lastname |
|
363 | 362 | assert new_user.email == email |
|
364 | 363 | |
|
365 | 364 | response = self.app.get(route_path('users_data'), |
|
366 | 365 | extra_environ=xhr_header) |
|
367 | 366 | response.mustcontain(username) |
|
368 | 367 | |
|
369 | 368 | def test_create_err(self): |
|
370 | 369 | self.log_user() |
|
371 | 370 | username = 'new_user' |
|
372 | 371 | password = '' |
|
373 | 372 | name = 'name' |
|
374 | 373 | lastname = 'lastname' |
|
375 | 374 | email = 'errmail.com' |
|
376 | 375 | |
|
377 | 376 | self.app.get(route_path('users_new')) |
|
378 | 377 | |
|
379 | 378 | response = self.app.post(route_path('users_create'), params={ |
|
380 | 379 | 'username': username, |
|
381 | 380 | 'password': password, |
|
382 | 381 | 'name': name, |
|
383 | 382 | 'active': False, |
|
384 | 383 | 'lastname': lastname, |
|
385 | 384 | 'email': email, |
|
386 | 385 | 'csrf_token': self.csrf_token, |
|
387 | 386 | }) |
|
388 | 387 | |
|
389 | msg = validators.ValidUsername( | |
|
390 | False, {})._messages['system_invalid_username'] | |
|
388 | msg = '???' | |
|
391 | 389 | msg = h.html_escape(msg % {'username': 'new_user'}) |
|
392 | 390 | response.mustcontain('<span class="error-message">%s</span>' % msg) |
|
393 | 391 | response.mustcontain( |
|
394 | 392 | '<span class="error-message">Please enter a value</span>') |
|
395 | 393 | response.mustcontain( |
|
396 | 394 | '<span class="error-message">An email address must contain a' |
|
397 | 395 | ' single @</span>') |
|
398 | 396 | |
|
399 | 397 | def get_user(): |
|
400 | 398 | Session().query(User).filter(User.username == username).one() |
|
401 | 399 | |
|
402 | 400 | with pytest.raises(NoResultFound): |
|
403 | 401 | get_user() |
|
404 | 402 | |
|
405 | 403 | def test_new(self): |
|
406 | 404 | self.log_user() |
|
407 | 405 | self.app.get(route_path('users_new')) |
|
408 | 406 | |
|
409 | 407 | @pytest.mark.parametrize("name, attrs", [ |
|
410 | 408 | ('firstname', {'firstname': 'new_username'}), |
|
411 | 409 | ('lastname', {'lastname': 'new_username'}), |
|
412 | 410 | ('admin', {'admin': True}), |
|
413 | 411 | ('admin', {'admin': False}), |
|
414 | 412 | ('extern_type', {'extern_type': 'ldap'}), |
|
415 | 413 | ('extern_type', {'extern_type': None}), |
|
416 | 414 | ('extern_name', {'extern_name': 'test'}), |
|
417 | 415 | ('extern_name', {'extern_name': None}), |
|
418 | 416 | ('active', {'active': False}), |
|
419 | 417 | ('active', {'active': True}), |
|
420 | 418 | ('email', {'email': 'some@email.com'}), |
|
421 | 419 | ('language', {'language': 'de'}), |
|
422 | 420 | ('language', {'language': 'en'}), |
|
423 | 421 | # ('new_password', {'new_password': 'foobar123', |
|
424 | 422 | # 'password_confirmation': 'foobar123'}) |
|
425 | 423 | ]) |
|
426 | 424 | def test_update(self, name, attrs, user_util): |
|
427 | 425 | self.log_user() |
|
428 | 426 | usr = user_util.create_user( |
|
429 | 427 | password='qweqwe', |
|
430 | 428 | email='testme@rhodecode.org', |
|
431 | 429 | extern_type='rhodecode', |
|
432 | 430 | extern_name='xxx', |
|
433 | 431 | ) |
|
434 | 432 | user_id = usr.user_id |
|
435 | 433 | Session().commit() |
|
436 | 434 | |
|
437 | 435 | params = usr.get_api_data() |
|
438 | 436 | cur_lang = params['language'] or 'en' |
|
439 | 437 | params.update({ |
|
440 | 438 | 'password_confirmation': '', |
|
441 | 439 | 'new_password': '', |
|
442 | 440 | 'language': cur_lang, |
|
443 | 441 | 'csrf_token': self.csrf_token, |
|
444 | 442 | }) |
|
445 | 443 | params.update({'new_password': ''}) |
|
446 | 444 | params.update(attrs) |
|
447 | 445 | if name == 'email': |
|
448 | 446 | params['emails'] = [attrs['email']] |
|
449 | 447 | elif name == 'extern_type': |
|
450 | 448 | # cannot update this via form, expected value is original one |
|
451 | 449 | params['extern_type'] = "rhodecode" |
|
452 | 450 | elif name == 'extern_name': |
|
453 | 451 | # cannot update this via form, expected value is original one |
|
454 | 452 | params['extern_name'] = 'xxx' |
|
455 | 453 | # special case since this user is not |
|
456 | 454 | # logged in yet his data is not filled |
|
457 | 455 | # so we use creation data |
|
458 | 456 | |
|
459 | 457 | response = self.app.post( |
|
460 | 458 | route_path('user_update', user_id=usr.user_id), params) |
|
461 | 459 | assert response.status_int == 302 |
|
462 | 460 | assert_session_flash(response, 'User updated successfully') |
|
463 | 461 | |
|
464 | 462 | updated_user = User.get(user_id) |
|
465 | 463 | updated_params = updated_user.get_api_data() |
|
466 | 464 | updated_params.update({'password_confirmation': ''}) |
|
467 | 465 | updated_params.update({'new_password': ''}) |
|
468 | 466 | |
|
469 | 467 | del params['csrf_token'] |
|
470 | 468 | assert params == updated_params |
|
471 | 469 | |
|
472 | 470 | def test_update_and_migrate_password( |
|
473 | 471 | self, autologin_user, real_crypto_backend, user_util): |
|
474 | 472 | |
|
475 | 473 | user = user_util.create_user() |
|
476 | 474 | temp_user = user.username |
|
477 | 475 | user.password = auth._RhodeCodeCryptoSha256().hash_create( |
|
478 | 476 | b'test123') |
|
479 | 477 | Session().add(user) |
|
480 | 478 | Session().commit() |
|
481 | 479 | |
|
482 | 480 | params = user.get_api_data() |
|
483 | 481 | |
|
484 | 482 | params.update({ |
|
485 | 483 | 'password_confirmation': 'qweqwe123', |
|
486 | 484 | 'new_password': 'qweqwe123', |
|
487 | 485 | 'language': 'en', |
|
488 | 486 | 'csrf_token': autologin_user.csrf_token, |
|
489 | 487 | }) |
|
490 | 488 | |
|
491 | 489 | response = self.app.post( |
|
492 | 490 | route_path('user_update', user_id=user.user_id), params) |
|
493 | 491 | assert response.status_int == 302 |
|
494 | 492 | assert_session_flash(response, 'User updated successfully') |
|
495 | 493 | |
|
496 | 494 | # new password should be bcrypted, after log-in and transfer |
|
497 | 495 | user = User.get_by_username(temp_user) |
|
498 | 496 | assert user.password.startswith('$') |
|
499 | 497 | |
|
500 | 498 | updated_user = User.get_by_username(temp_user) |
|
501 | 499 | updated_params = updated_user.get_api_data() |
|
502 | 500 | updated_params.update({'password_confirmation': 'qweqwe123'}) |
|
503 | 501 | updated_params.update({'new_password': 'qweqwe123'}) |
|
504 | 502 | |
|
505 | 503 | del params['csrf_token'] |
|
506 | 504 | assert params == updated_params |
|
507 | 505 | |
|
508 | 506 | def test_delete(self): |
|
509 | 507 | self.log_user() |
|
510 | 508 | username = 'newtestuserdeleteme' |
|
511 | 509 | |
|
512 | 510 | fixture.create_user(name=username) |
|
513 | 511 | |
|
514 | 512 | new_user = Session().query(User)\ |
|
515 | 513 | .filter(User.username == username).one() |
|
516 | 514 | response = self.app.post( |
|
517 | 515 | route_path('user_delete', user_id=new_user.user_id), |
|
518 | 516 | params={'csrf_token': self.csrf_token}) |
|
519 | 517 | |
|
520 | 518 | assert_session_flash(response, 'Successfully deleted user') |
|
521 | 519 | |
|
522 | 520 | def test_delete_owner_of_repository(self, request, user_util): |
|
523 | 521 | self.log_user() |
|
524 | 522 | obj_name = 'test_repo' |
|
525 | 523 | usr = user_util.create_user() |
|
526 | 524 | username = usr.username |
|
527 | 525 | fixture.create_repo(obj_name, cur_user=usr.username) |
|
528 | 526 | |
|
529 | 527 | new_user = Session().query(User)\ |
|
530 | 528 | .filter(User.username == username).one() |
|
531 | 529 | response = self.app.post( |
|
532 | 530 | route_path('user_delete', user_id=new_user.user_id), |
|
533 | 531 | params={'csrf_token': self.csrf_token}) |
|
534 | 532 | |
|
535 | 533 | msg = 'user "%s" still owns 1 repositories and cannot be removed. ' \ |
|
536 | 534 | 'Switch owners or remove those repositories:%s' % (username, |
|
537 | 535 | obj_name) |
|
538 | 536 | assert_session_flash(response, msg) |
|
539 | 537 | fixture.destroy_repo(obj_name) |
|
540 | 538 | |
|
541 | 539 | def test_delete_owner_of_repository_detaching(self, request, user_util): |
|
542 | 540 | self.log_user() |
|
543 | 541 | obj_name = 'test_repo' |
|
544 | 542 | usr = user_util.create_user(auto_cleanup=False) |
|
545 | 543 | username = usr.username |
|
546 | 544 | fixture.create_repo(obj_name, cur_user=usr.username) |
|
547 | 545 | |
|
548 | 546 | new_user = Session().query(User)\ |
|
549 | 547 | .filter(User.username == username).one() |
|
550 | 548 | response = self.app.post( |
|
551 | 549 | route_path('user_delete', user_id=new_user.user_id), |
|
552 | 550 | params={'user_repos': 'detach', 'csrf_token': self.csrf_token}) |
|
553 | 551 | |
|
554 | 552 | msg = 'Detached 1 repositories' |
|
555 | 553 | assert_session_flash(response, msg) |
|
556 | 554 | fixture.destroy_repo(obj_name) |
|
557 | 555 | |
|
558 | 556 | def test_delete_owner_of_repository_deleting(self, request, user_util): |
|
559 | 557 | self.log_user() |
|
560 | 558 | obj_name = 'test_repo' |
|
561 | 559 | usr = user_util.create_user(auto_cleanup=False) |
|
562 | 560 | username = usr.username |
|
563 | 561 | fixture.create_repo(obj_name, cur_user=usr.username) |
|
564 | 562 | |
|
565 | 563 | new_user = Session().query(User)\ |
|
566 | 564 | .filter(User.username == username).one() |
|
567 | 565 | response = self.app.post( |
|
568 | 566 | route_path('user_delete', user_id=new_user.user_id), |
|
569 | 567 | params={'user_repos': 'delete', 'csrf_token': self.csrf_token}) |
|
570 | 568 | |
|
571 | 569 | msg = 'Deleted 1 repositories' |
|
572 | 570 | assert_session_flash(response, msg) |
|
573 | 571 | |
|
574 | 572 | def test_delete_owner_of_repository_group(self, request, user_util): |
|
575 | 573 | self.log_user() |
|
576 | 574 | obj_name = 'test_group' |
|
577 | 575 | usr = user_util.create_user() |
|
578 | 576 | username = usr.username |
|
579 | 577 | fixture.create_repo_group(obj_name, cur_user=usr.username) |
|
580 | 578 | |
|
581 | 579 | new_user = Session().query(User)\ |
|
582 | 580 | .filter(User.username == username).one() |
|
583 | 581 | response = self.app.post( |
|
584 | 582 | route_path('user_delete', user_id=new_user.user_id), |
|
585 | 583 | params={'csrf_token': self.csrf_token}) |
|
586 | 584 | |
|
587 | 585 | msg = 'user "%s" still owns 1 repository groups and cannot be removed. ' \ |
|
588 | 586 | 'Switch owners or remove those repository groups:%s' % (username, |
|
589 | 587 | obj_name) |
|
590 | 588 | assert_session_flash(response, msg) |
|
591 | 589 | fixture.destroy_repo_group(obj_name) |
|
592 | 590 | |
|
593 | 591 | def test_delete_owner_of_repository_group_detaching(self, request, user_util): |
|
594 | 592 | self.log_user() |
|
595 | 593 | obj_name = 'test_group' |
|
596 | 594 | usr = user_util.create_user(auto_cleanup=False) |
|
597 | 595 | username = usr.username |
|
598 | 596 | fixture.create_repo_group(obj_name, cur_user=usr.username) |
|
599 | 597 | |
|
600 | 598 | new_user = Session().query(User)\ |
|
601 | 599 | .filter(User.username == username).one() |
|
602 | 600 | response = self.app.post( |
|
603 | 601 | route_path('user_delete', user_id=new_user.user_id), |
|
604 | 602 | params={'user_repo_groups': 'delete', 'csrf_token': self.csrf_token}) |
|
605 | 603 | |
|
606 | 604 | msg = 'Deleted 1 repository groups' |
|
607 | 605 | assert_session_flash(response, msg) |
|
608 | 606 | |
|
609 | 607 | def test_delete_owner_of_repository_group_deleting(self, request, user_util): |
|
610 | 608 | self.log_user() |
|
611 | 609 | obj_name = 'test_group' |
|
612 | 610 | usr = user_util.create_user(auto_cleanup=False) |
|
613 | 611 | username = usr.username |
|
614 | 612 | fixture.create_repo_group(obj_name, cur_user=usr.username) |
|
615 | 613 | |
|
616 | 614 | new_user = Session().query(User)\ |
|
617 | 615 | .filter(User.username == username).one() |
|
618 | 616 | response = self.app.post( |
|
619 | 617 | route_path('user_delete', user_id=new_user.user_id), |
|
620 | 618 | params={'user_repo_groups': 'detach', 'csrf_token': self.csrf_token}) |
|
621 | 619 | |
|
622 | 620 | msg = 'Detached 1 repository groups' |
|
623 | 621 | assert_session_flash(response, msg) |
|
624 | 622 | fixture.destroy_repo_group(obj_name) |
|
625 | 623 | |
|
626 | 624 | def test_delete_owner_of_user_group(self, request, user_util): |
|
627 | 625 | self.log_user() |
|
628 | 626 | obj_name = 'test_user_group' |
|
629 | 627 | usr = user_util.create_user() |
|
630 | 628 | username = usr.username |
|
631 | 629 | fixture.create_user_group(obj_name, cur_user=usr.username) |
|
632 | 630 | |
|
633 | 631 | new_user = Session().query(User)\ |
|
634 | 632 | .filter(User.username == username).one() |
|
635 | 633 | response = self.app.post( |
|
636 | 634 | route_path('user_delete', user_id=new_user.user_id), |
|
637 | 635 | params={'csrf_token': self.csrf_token}) |
|
638 | 636 | |
|
639 | 637 | msg = 'user "%s" still owns 1 user groups and cannot be removed. ' \ |
|
640 | 638 | 'Switch owners or remove those user groups:%s' % (username, |
|
641 | 639 | obj_name) |
|
642 | 640 | assert_session_flash(response, msg) |
|
643 | 641 | fixture.destroy_user_group(obj_name) |
|
644 | 642 | |
|
645 | 643 | def test_delete_owner_of_user_group_detaching(self, request, user_util): |
|
646 | 644 | self.log_user() |
|
647 | 645 | obj_name = 'test_user_group' |
|
648 | 646 | usr = user_util.create_user(auto_cleanup=False) |
|
649 | 647 | username = usr.username |
|
650 | 648 | fixture.create_user_group(obj_name, cur_user=usr.username) |
|
651 | 649 | |
|
652 | 650 | new_user = Session().query(User)\ |
|
653 | 651 | .filter(User.username == username).one() |
|
654 | 652 | try: |
|
655 | 653 | response = self.app.post( |
|
656 | 654 | route_path('user_delete', user_id=new_user.user_id), |
|
657 | 655 | params={'user_user_groups': 'detach', |
|
658 | 656 | 'csrf_token': self.csrf_token}) |
|
659 | 657 | |
|
660 | 658 | msg = 'Detached 1 user groups' |
|
661 | 659 | assert_session_flash(response, msg) |
|
662 | 660 | finally: |
|
663 | 661 | fixture.destroy_user_group(obj_name) |
|
664 | 662 | |
|
665 | 663 | def test_delete_owner_of_user_group_deleting(self, request, user_util): |
|
666 | 664 | self.log_user() |
|
667 | 665 | obj_name = 'test_user_group' |
|
668 | 666 | usr = user_util.create_user(auto_cleanup=False) |
|
669 | 667 | username = usr.username |
|
670 | 668 | fixture.create_user_group(obj_name, cur_user=usr.username) |
|
671 | 669 | |
|
672 | 670 | new_user = Session().query(User)\ |
|
673 | 671 | .filter(User.username == username).one() |
|
674 | 672 | response = self.app.post( |
|
675 | 673 | route_path('user_delete', user_id=new_user.user_id), |
|
676 | 674 | params={'user_user_groups': 'delete', 'csrf_token': self.csrf_token}) |
|
677 | 675 | |
|
678 | 676 | msg = 'Deleted 1 user groups' |
|
679 | 677 | assert_session_flash(response, msg) |
|
680 | 678 | |
|
681 | 679 | def test_edit(self, user_util): |
|
682 | 680 | self.log_user() |
|
683 | 681 | user = user_util.create_user() |
|
684 | 682 | self.app.get(route_path('user_edit', user_id=user.user_id)) |
|
685 | 683 | |
|
686 | 684 | def test_edit_default_user_redirect(self): |
|
687 | 685 | self.log_user() |
|
688 | 686 | user = User.get_default_user() |
|
689 | 687 | self.app.get(route_path('user_edit', user_id=user.user_id), status=302) |
|
690 | 688 | |
|
691 | 689 | @pytest.mark.parametrize( |
|
692 | 690 | 'repo_create, repo_create_write, user_group_create, repo_group_create,' |
|
693 | 691 | 'fork_create, inherit_default_permissions, expect_error,' |
|
694 | 692 | 'expect_form_error', [ |
|
695 | 693 | ('hg.create.none', 'hg.create.write_on_repogroup.false', |
|
696 | 694 | 'hg.usergroup.create.false', 'hg.repogroup.create.false', |
|
697 | 695 | 'hg.fork.none', 'hg.inherit_default_perms.false', False, False), |
|
698 | 696 | ('hg.create.repository', 'hg.create.write_on_repogroup.false', |
|
699 | 697 | 'hg.usergroup.create.false', 'hg.repogroup.create.false', |
|
700 | 698 | 'hg.fork.none', 'hg.inherit_default_perms.false', False, False), |
|
701 | 699 | ('hg.create.repository', 'hg.create.write_on_repogroup.true', |
|
702 | 700 | 'hg.usergroup.create.true', 'hg.repogroup.create.true', |
|
703 | 701 | 'hg.fork.repository', 'hg.inherit_default_perms.false', False, |
|
704 | 702 | False), |
|
705 | 703 | ('hg.create.XXX', 'hg.create.write_on_repogroup.true', |
|
706 | 704 | 'hg.usergroup.create.true', 'hg.repogroup.create.true', |
|
707 | 705 | 'hg.fork.repository', 'hg.inherit_default_perms.false', False, |
|
708 | 706 | True), |
|
709 | 707 | ('', '', '', '', '', '', True, False), |
|
710 | 708 | ]) |
|
711 | 709 | def test_global_perms_on_user( |
|
712 | 710 | self, repo_create, repo_create_write, user_group_create, |
|
713 | 711 | repo_group_create, fork_create, expect_error, expect_form_error, |
|
714 | 712 | inherit_default_permissions, user_util): |
|
715 | 713 | self.log_user() |
|
716 | 714 | user = user_util.create_user() |
|
717 | 715 | uid = user.user_id |
|
718 | 716 | |
|
719 | 717 | # ENABLE REPO CREATE ON A GROUP |
|
720 | 718 | perm_params = { |
|
721 | 719 | 'inherit_default_permissions': False, |
|
722 | 720 | 'default_repo_create': repo_create, |
|
723 | 721 | 'default_repo_create_on_write': repo_create_write, |
|
724 | 722 | 'default_user_group_create': user_group_create, |
|
725 | 723 | 'default_repo_group_create': repo_group_create, |
|
726 | 724 | 'default_fork_create': fork_create, |
|
727 | 725 | 'default_inherit_default_permissions': inherit_default_permissions, |
|
728 | 726 | 'csrf_token': self.csrf_token, |
|
729 | 727 | } |
|
730 | 728 | response = self.app.post( |
|
731 | 729 | route_path('user_edit_global_perms_update', user_id=uid), |
|
732 | 730 | params=perm_params) |
|
733 | 731 | |
|
734 | 732 | if expect_form_error: |
|
735 | 733 | assert response.status_int == 200 |
|
736 | 734 | response.mustcontain('Value must be one of') |
|
737 | 735 | else: |
|
738 | 736 | if expect_error: |
|
739 | 737 | msg = 'An error occurred during permissions saving' |
|
740 | 738 | else: |
|
741 | 739 | msg = 'User global permissions updated successfully' |
|
742 | 740 | ug = User.get(uid) |
|
743 | 741 | del perm_params['inherit_default_permissions'] |
|
744 | 742 | del perm_params['csrf_token'] |
|
745 | 743 | assert perm_params == ug.get_default_perms() |
|
746 | 744 | assert_session_flash(response, msg) |
|
747 | 745 | |
|
748 | 746 | def test_global_permissions_initial_values(self, user_util): |
|
749 | 747 | self.log_user() |
|
750 | 748 | user = user_util.create_user() |
|
751 | 749 | uid = user.user_id |
|
752 | 750 | response = self.app.get( |
|
753 | 751 | route_path('user_edit_global_perms', user_id=uid)) |
|
754 | 752 | default_user = User.get_default_user() |
|
755 | 753 | default_permissions = default_user.get_default_perms() |
|
756 | 754 | assert_response = response.assert_response() |
|
757 | 755 | expected_permissions = ( |
|
758 | 756 | 'default_repo_create', 'default_repo_create_on_write', |
|
759 | 757 | 'default_fork_create', 'default_repo_group_create', |
|
760 | 758 | 'default_user_group_create', 'default_inherit_default_permissions') |
|
761 | 759 | for permission in expected_permissions: |
|
762 | 760 | css_selector = '[name={}][checked=checked]'.format(permission) |
|
763 | 761 | element = assert_response.get_element(css_selector) |
|
764 | 762 | assert element.value == default_permissions[permission] |
|
765 | 763 | |
|
766 | 764 | def test_perms_summary_page(self): |
|
767 | 765 | user = self.log_user() |
|
768 | 766 | response = self.app.get( |
|
769 | 767 | route_path('edit_user_perms_summary', user_id=user['user_id'])) |
|
770 | 768 | for repo in Repository.query().all(): |
|
771 | 769 | response.mustcontain(repo.repo_name) |
|
772 | 770 | |
|
773 | 771 | def test_perms_summary_page_json(self): |
|
774 | 772 | user = self.log_user() |
|
775 | 773 | response = self.app.get( |
|
776 | 774 | route_path('edit_user_perms_summary_json', user_id=user['user_id'])) |
|
777 | 775 | for repo in Repository.query().all(): |
|
778 | 776 | response.mustcontain(repo.repo_name) |
|
779 | 777 | |
|
780 | 778 | def test_audit_log_page(self): |
|
781 | 779 | user = self.log_user() |
|
782 | 780 | self.app.get( |
|
783 | 781 | route_path('edit_user_audit_logs', user_id=user['user_id'])) |
@@ -1,91 +1,90 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | |
|
3 | 3 | # Copyright (C) 2016-2017 RhodeCode GmbH |
|
4 | 4 | # |
|
5 | 5 | # This program is free software: you can redistribute it and/or modify |
|
6 | 6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
7 | 7 | # (only), as published by the Free Software Foundation. |
|
8 | 8 | # |
|
9 | 9 | # This program is distributed in the hope that it will be useful, |
|
10 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 | 12 | # GNU General Public License for more details. |
|
13 | 13 | # |
|
14 | 14 | # You should have received a copy of the GNU Affero General Public License |
|
15 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | 16 | # |
|
17 | 17 | # This program is dual-licensed. If you wish to learn more about the |
|
18 | 18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
19 | 19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
20 | 20 | |
|
21 | 21 | import logging |
|
22 | 22 | |
|
23 | 23 | from pyramid.httpexceptions import HTTPNotFound |
|
24 | 24 | from pyramid.view import view_config |
|
25 | 25 | |
|
26 | 26 | from rhodecode.apps._base import BaseAppView |
|
27 | 27 | from rhodecode.model.db import joinedload, UserLog |
|
28 | 28 | from rhodecode.lib.user_log_filter import user_log_filter |
|
29 | 29 | from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator |
|
30 | 30 | from rhodecode.lib.utils2 import safe_int |
|
31 | 31 | from rhodecode.lib.helpers import Page |
|
32 | 32 | |
|
33 | 33 | log = logging.getLogger(__name__) |
|
34 | 34 | |
|
35 | 35 | |
|
36 | 36 | class AdminAuditLogsView(BaseAppView): |
|
37 | 37 | def load_default_context(self): |
|
38 | 38 | c = self._get_local_tmpl_context() |
|
39 | self._register_global_c(c) | |
|
40 | 39 | return c |
|
41 | 40 | |
|
42 | 41 | @LoginRequired() |
|
43 | 42 | @HasPermissionAllDecorator('hg.admin') |
|
44 | 43 | @view_config( |
|
45 | 44 | route_name='admin_audit_logs', request_method='GET', |
|
46 | 45 | renderer='rhodecode:templates/admin/admin_audit_logs.mako') |
|
47 | 46 | def admin_audit_logs(self): |
|
48 | 47 | c = self.load_default_context() |
|
49 | 48 | |
|
50 | 49 | users_log = UserLog.query()\ |
|
51 | 50 | .options(joinedload(UserLog.user))\ |
|
52 | 51 | .options(joinedload(UserLog.repository)) |
|
53 | 52 | |
|
54 | 53 | # FILTERING |
|
55 | 54 | c.search_term = self.request.GET.get('filter') |
|
56 | 55 | try: |
|
57 | 56 | users_log = user_log_filter(users_log, c.search_term) |
|
58 | 57 | except Exception: |
|
59 | 58 | # we want this to crash for now |
|
60 | 59 | raise |
|
61 | 60 | |
|
62 | 61 | users_log = users_log.order_by(UserLog.action_date.desc()) |
|
63 | 62 | |
|
64 | 63 | p = safe_int(self.request.GET.get('page', 1), 1) |
|
65 | 64 | |
|
66 | 65 | def url_generator(**kw): |
|
67 | 66 | if c.search_term: |
|
68 | 67 | kw['filter'] = c.search_term |
|
69 | 68 | return self.request.current_route_path(_query=kw) |
|
70 | 69 | |
|
71 | 70 | c.audit_logs = Page(users_log, page=p, items_per_page=10, |
|
72 | 71 | url=url_generator) |
|
73 | 72 | return self._get_template_context(c) |
|
74 | 73 | |
|
75 | 74 | @LoginRequired() |
|
76 | 75 | @HasPermissionAllDecorator('hg.admin') |
|
77 | 76 | @view_config( |
|
78 | 77 | route_name='admin_audit_log_entry', request_method='GET', |
|
79 | 78 | renderer='rhodecode:templates/admin/admin_audit_log_entry.mako') |
|
80 | 79 | def admin_audit_log_entry(self): |
|
81 | 80 | c = self.load_default_context() |
|
82 | 81 | audit_log_id = self.request.matchdict['audit_log_id'] |
|
83 | 82 | |
|
84 | 83 | c.audit_log_entry = UserLog.query()\ |
|
85 | 84 | .options(joinedload(UserLog.user))\ |
|
86 | 85 | .options(joinedload(UserLog.repository))\ |
|
87 | 86 | .filter(UserLog.user_log_id == audit_log_id).scalar() |
|
88 | 87 | if not c.audit_log_entry: |
|
89 | 88 | raise HTTPNotFound() |
|
90 | 89 | |
|
91 | 90 | return self._get_template_context(c) |
@@ -1,111 +1,111 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | |
|
3 | 3 | # Copyright (C) 2016-2017 RhodeCode GmbH |
|
4 | 4 | # |
|
5 | 5 | # This program is free software: you can redistribute it and/or modify |
|
6 | 6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
7 | 7 | # (only), as published by the Free Software Foundation. |
|
8 | 8 | # |
|
9 | 9 | # This program is distributed in the hope that it will be useful, |
|
10 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 | 12 | # GNU General Public License for more details. |
|
13 | 13 | # |
|
14 | 14 | # You should have received a copy of the GNU Affero General Public License |
|
15 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | 16 | # |
|
17 | 17 | # This program is dual-licensed. If you wish to learn more about the |
|
18 | 18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
19 | 19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
20 | 20 | |
|
21 | 21 | import logging |
|
22 | 22 | |
|
23 | 23 | import formencode |
|
24 | 24 | import formencode.htmlfill |
|
25 | 25 | |
|
26 | 26 | from pyramid.view import view_config |
|
27 | 27 | from pyramid.httpexceptions import HTTPFound |
|
28 | 28 | from pyramid.renderers import render |
|
29 | 29 | from pyramid.response import Response |
|
30 | 30 | |
|
31 | 31 | from rhodecode.apps._base import BaseAppView |
|
32 | 32 | from rhodecode.lib.auth import ( |
|
33 | 33 | LoginRequired, HasPermissionAllDecorator, CSRFRequired) |
|
34 | 34 | from rhodecode.lib import helpers as h |
|
35 | 35 | from rhodecode.model.forms import DefaultsForm |
|
36 | 36 | from rhodecode.model.meta import Session |
|
37 | 37 | from rhodecode import BACKENDS |
|
38 | 38 | from rhodecode.model.settings import SettingsModel |
|
39 | 39 | |
|
40 | 40 | log = logging.getLogger(__name__) |
|
41 | 41 | |
|
42 | 42 | |
|
43 | 43 | class AdminDefaultSettingsView(BaseAppView): |
|
44 | 44 | def load_default_context(self): |
|
45 | 45 | c = self._get_local_tmpl_context() |
|
46 | 46 | |
|
47 | self._register_global_c(c) | |
|
47 | ||
|
48 | 48 | return c |
|
49 | 49 | |
|
50 | 50 | @LoginRequired() |
|
51 | 51 | @HasPermissionAllDecorator('hg.admin') |
|
52 | 52 | @view_config( |
|
53 | 53 | route_name='admin_defaults_repositories', request_method='GET', |
|
54 | 54 | renderer='rhodecode:templates/admin/defaults/defaults.mako') |
|
55 | 55 | def defaults_repository_show(self): |
|
56 | 56 | c = self.load_default_context() |
|
57 | 57 | c.backends = BACKENDS.keys() |
|
58 | 58 | c.active = 'repositories' |
|
59 | 59 | defaults = SettingsModel().get_default_repo_settings() |
|
60 | 60 | |
|
61 | 61 | data = render( |
|
62 | 62 | 'rhodecode:templates/admin/defaults/defaults.mako', |
|
63 | 63 | self._get_template_context(c), self.request) |
|
64 | 64 | html = formencode.htmlfill.render( |
|
65 | 65 | data, |
|
66 | 66 | defaults=defaults, |
|
67 | 67 | encoding="UTF-8", |
|
68 | 68 | force_defaults=False |
|
69 | 69 | ) |
|
70 | 70 | return Response(html) |
|
71 | 71 | |
|
72 | 72 | @LoginRequired() |
|
73 | 73 | @HasPermissionAllDecorator('hg.admin') |
|
74 | 74 | @CSRFRequired() |
|
75 | 75 | @view_config( |
|
76 | 76 | route_name='admin_defaults_repositories_update', request_method='POST', |
|
77 | 77 | renderer='rhodecode:templates/admin/defaults/defaults.mako') |
|
78 | 78 | def defaults_repository_update(self): |
|
79 | 79 | _ = self.request.translate |
|
80 | 80 | c = self.load_default_context() |
|
81 | 81 | c.active = 'repositories' |
|
82 | form = DefaultsForm()() | |
|
82 | form = DefaultsForm(self.request.translate)() | |
|
83 | 83 | |
|
84 | 84 | try: |
|
85 | 85 | form_result = form.to_python(dict(self.request.POST)) |
|
86 | 86 | for k, v in form_result.iteritems(): |
|
87 | 87 | setting = SettingsModel().create_or_update_setting(k, v) |
|
88 | 88 | Session().add(setting) |
|
89 | 89 | Session().commit() |
|
90 | 90 | h.flash(_('Default settings updated successfully'), |
|
91 | 91 | category='success') |
|
92 | 92 | |
|
93 | 93 | except formencode.Invalid as errors: |
|
94 | 94 | data = render( |
|
95 | 95 | 'rhodecode:templates/admin/defaults/defaults.mako', |
|
96 | 96 | self._get_template_context(c), self.request) |
|
97 | 97 | html = formencode.htmlfill.render( |
|
98 | 98 | data, |
|
99 | 99 | defaults=errors.value, |
|
100 | 100 | errors=errors.error_dict or {}, |
|
101 | 101 | prefix_error=False, |
|
102 | 102 | encoding="UTF-8", |
|
103 | 103 | force_defaults=False |
|
104 | 104 | ) |
|
105 | 105 | return Response(html) |
|
106 | 106 | except Exception: |
|
107 | 107 | log.exception('Exception in update action') |
|
108 | 108 | h.flash(_('Error occurred during update of default values'), |
|
109 | 109 | category='error') |
|
110 | 110 | |
|
111 | 111 | raise HTTPFound(h.route_path('admin_defaults_repositories')) |
@@ -1,53 +1,53 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | |
|
3 | 3 | # Copyright (C) 2016-2017 RhodeCode GmbH |
|
4 | 4 | # |
|
5 | 5 | # This program is free software: you can redistribute it and/or modify |
|
6 | 6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
7 | 7 | # (only), as published by the Free Software Foundation. |
|
8 | 8 | # |
|
9 | 9 | # This program is distributed in the hope that it will be useful, |
|
10 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 | 12 | # GNU General Public License for more details. |
|
13 | 13 | # |
|
14 | 14 | # You should have received a copy of the GNU Affero General Public License |
|
15 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | 16 | # |
|
17 | 17 | # This program is dual-licensed. If you wish to learn more about the |
|
18 | 18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
19 | 19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
20 | 20 | |
|
21 | 21 | import collections |
|
22 | 22 | import logging |
|
23 | 23 | |
|
24 | 24 | from pyramid.view import view_config |
|
25 | 25 | |
|
26 | 26 | from rhodecode.apps._base import BaseAppView |
|
27 | 27 | from rhodecode.apps.admin.navigation import navigation_list |
|
28 | 28 | from rhodecode.lib.auth import (LoginRequired, HasPermissionAllDecorator) |
|
29 | 29 | from rhodecode.lib.utils import read_opensource_licenses |
|
30 | 30 | |
|
31 | 31 | log = logging.getLogger(__name__) |
|
32 | 32 | |
|
33 | 33 | |
|
34 | 34 | class OpenSourceLicensesAdminSettingsView(BaseAppView): |
|
35 | 35 | |
|
36 | 36 | def load_default_context(self): |
|
37 | 37 | c = self._get_local_tmpl_context() |
|
38 | self._register_global_c(c) | |
|
38 | ||
|
39 | 39 | return c |
|
40 | 40 | |
|
41 | 41 | @LoginRequired() |
|
42 | 42 | @HasPermissionAllDecorator('hg.admin') |
|
43 | 43 | @view_config( |
|
44 | 44 | route_name='admin_settings_open_source', request_method='GET', |
|
45 | 45 | renderer='rhodecode:templates/admin/settings/settings.mako') |
|
46 | 46 | def open_source_licenses(self): |
|
47 | 47 | c = self.load_default_context() |
|
48 | 48 | c.active = 'open_source' |
|
49 | 49 | c.navlist = navigation_list(self.request) |
|
50 | 50 | items = sorted(read_opensource_licenses().items(), key=lambda t: t[0]) |
|
51 | 51 | c.opensource_licenses = collections.OrderedDict(items) |
|
52 | 52 | |
|
53 | 53 | return self._get_template_context(c) |
@@ -1,482 +1,486 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | |
|
3 | 3 | # Copyright (C) 2016-2017 RhodeCode GmbH |
|
4 | 4 | # |
|
5 | 5 | # This program is free software: you can redistribute it and/or modify |
|
6 | 6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
7 | 7 | # (only), as published by the Free Software Foundation. |
|
8 | 8 | # |
|
9 | 9 | # This program is distributed in the hope that it will be useful, |
|
10 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 | 12 | # GNU General Public License for more details. |
|
13 | 13 | # |
|
14 | 14 | # You should have received a copy of the GNU Affero General Public License |
|
15 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | 16 | # |
|
17 | 17 | # This program is dual-licensed. If you wish to learn more about the |
|
18 | 18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
19 | 19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
20 | 20 | |
|
21 | 21 | import re |
|
22 | 22 | import logging |
|
23 | 23 | import formencode |
|
24 | 24 | import formencode.htmlfill |
|
25 | 25 | import datetime |
|
26 | 26 | from pyramid.interfaces import IRoutesMapper |
|
27 | 27 | |
|
28 | 28 | from pyramid.view import view_config |
|
29 | 29 | from pyramid.httpexceptions import HTTPFound |
|
30 | 30 | from pyramid.renderers import render |
|
31 | 31 | from pyramid.response import Response |
|
32 | 32 | |
|
33 | 33 | from rhodecode.apps._base import BaseAppView, DataGridAppView |
|
34 | 34 | from rhodecode.apps.ssh_support import SshKeyFileChangeEvent |
|
35 | 35 | from rhodecode.events import trigger |
|
36 | 36 | |
|
37 | 37 | from rhodecode.lib import helpers as h |
|
38 | 38 | from rhodecode.lib.auth import ( |
|
39 | 39 | LoginRequired, HasPermissionAllDecorator, CSRFRequired) |
|
40 | 40 | from rhodecode.lib.utils2 import aslist, safe_unicode |
|
41 | 41 | from rhodecode.model.db import ( |
|
42 | 42 | or_, coalesce, User, UserIpMap, UserSshKeys) |
|
43 | 43 | from rhodecode.model.forms import ( |
|
44 | 44 | ApplicationPermissionsForm, ObjectPermissionsForm, UserPermissionsForm) |
|
45 | 45 | from rhodecode.model.meta import Session |
|
46 | 46 | from rhodecode.model.permission import PermissionModel |
|
47 | 47 | from rhodecode.model.settings import SettingsModel |
|
48 | 48 | |
|
49 | 49 | |
|
50 | 50 | log = logging.getLogger(__name__) |
|
51 | 51 | |
|
52 | 52 | |
|
53 | 53 | class AdminPermissionsView(BaseAppView, DataGridAppView): |
|
54 | 54 | def load_default_context(self): |
|
55 | 55 | c = self._get_local_tmpl_context() |
|
56 | 56 | |
|
57 | self._register_global_c(c) | |
|
57 | ||
|
58 | 58 | PermissionModel().set_global_permission_choices( |
|
59 | 59 | c, gettext_translator=self.request.translate) |
|
60 | 60 | return c |
|
61 | 61 | |
|
62 | 62 | @LoginRequired() |
|
63 | 63 | @HasPermissionAllDecorator('hg.admin') |
|
64 | 64 | @view_config( |
|
65 | 65 | route_name='admin_permissions_application', request_method='GET', |
|
66 | 66 | renderer='rhodecode:templates/admin/permissions/permissions.mako') |
|
67 | 67 | def permissions_application(self): |
|
68 | 68 | c = self.load_default_context() |
|
69 | 69 | c.active = 'application' |
|
70 | 70 | |
|
71 | 71 | c.user = User.get_default_user(refresh=True) |
|
72 | 72 | |
|
73 | 73 | app_settings = SettingsModel().get_all_settings() |
|
74 | 74 | defaults = { |
|
75 | 75 | 'anonymous': c.user.active, |
|
76 | 76 | 'default_register_message': app_settings.get( |
|
77 | 77 | 'rhodecode_register_message') |
|
78 | 78 | } |
|
79 | 79 | defaults.update(c.user.get_default_perms()) |
|
80 | 80 | |
|
81 | 81 | data = render('rhodecode:templates/admin/permissions/permissions.mako', |
|
82 | 82 | self._get_template_context(c), self.request) |
|
83 | 83 | html = formencode.htmlfill.render( |
|
84 | 84 | data, |
|
85 | 85 | defaults=defaults, |
|
86 | 86 | encoding="UTF-8", |
|
87 | 87 | force_defaults=False |
|
88 | 88 | ) |
|
89 | 89 | return Response(html) |
|
90 | 90 | |
|
91 | 91 | @LoginRequired() |
|
92 | 92 | @HasPermissionAllDecorator('hg.admin') |
|
93 | 93 | @CSRFRequired() |
|
94 | 94 | @view_config( |
|
95 | 95 | route_name='admin_permissions_application_update', request_method='POST', |
|
96 | 96 | renderer='rhodecode:templates/admin/permissions/permissions.mako') |
|
97 | 97 | def permissions_application_update(self): |
|
98 | 98 | _ = self.request.translate |
|
99 | 99 | c = self.load_default_context() |
|
100 | 100 | c.active = 'application' |
|
101 | 101 | |
|
102 | 102 | _form = ApplicationPermissionsForm( |
|
103 | self.request.translate, | |
|
103 | 104 | [x[0] for x in c.register_choices], |
|
104 | 105 | [x[0] for x in c.password_reset_choices], |
|
105 | 106 | [x[0] for x in c.extern_activate_choices])() |
|
106 | 107 | |
|
107 | 108 | try: |
|
108 | 109 | form_result = _form.to_python(dict(self.request.POST)) |
|
109 | 110 | form_result.update({'perm_user_name': User.DEFAULT_USER}) |
|
110 | 111 | PermissionModel().update_application_permissions(form_result) |
|
111 | 112 | |
|
112 | 113 | settings = [ |
|
113 | 114 | ('register_message', 'default_register_message'), |
|
114 | 115 | ] |
|
115 | 116 | for setting, form_key in settings: |
|
116 | 117 | sett = SettingsModel().create_or_update_setting( |
|
117 | 118 | setting, form_result[form_key]) |
|
118 | 119 | Session().add(sett) |
|
119 | 120 | |
|
120 | 121 | Session().commit() |
|
121 | 122 | h.flash(_('Application permissions updated successfully'), |
|
122 | 123 | category='success') |
|
123 | 124 | |
|
124 | 125 | except formencode.Invalid as errors: |
|
125 | 126 | defaults = errors.value |
|
126 | 127 | |
|
127 | 128 | data = render( |
|
128 | 129 | 'rhodecode:templates/admin/permissions/permissions.mako', |
|
129 | 130 | self._get_template_context(c), self.request) |
|
130 | 131 | html = formencode.htmlfill.render( |
|
131 | 132 | data, |
|
132 | 133 | defaults=defaults, |
|
133 | 134 | errors=errors.error_dict or {}, |
|
134 | 135 | prefix_error=False, |
|
135 | 136 | encoding="UTF-8", |
|
136 | 137 | force_defaults=False |
|
137 | 138 | ) |
|
138 | 139 | return Response(html) |
|
139 | 140 | |
|
140 | 141 | except Exception: |
|
141 | 142 | log.exception("Exception during update of permissions") |
|
142 | 143 | h.flash(_('Error occurred during update of permissions'), |
|
143 | 144 | category='error') |
|
144 | 145 | |
|
145 | 146 | raise HTTPFound(h.route_path('admin_permissions_application')) |
|
146 | 147 | |
|
147 | 148 | @LoginRequired() |
|
148 | 149 | @HasPermissionAllDecorator('hg.admin') |
|
149 | 150 | @view_config( |
|
150 | 151 | route_name='admin_permissions_object', request_method='GET', |
|
151 | 152 | renderer='rhodecode:templates/admin/permissions/permissions.mako') |
|
152 | 153 | def permissions_objects(self): |
|
153 | 154 | c = self.load_default_context() |
|
154 | 155 | c.active = 'objects' |
|
155 | 156 | |
|
156 | 157 | c.user = User.get_default_user(refresh=True) |
|
157 | 158 | defaults = {} |
|
158 | 159 | defaults.update(c.user.get_default_perms()) |
|
159 | 160 | |
|
160 | 161 | data = render( |
|
161 | 162 | 'rhodecode:templates/admin/permissions/permissions.mako', |
|
162 | 163 | self._get_template_context(c), self.request) |
|
163 | 164 | html = formencode.htmlfill.render( |
|
164 | 165 | data, |
|
165 | 166 | defaults=defaults, |
|
166 | 167 | encoding="UTF-8", |
|
167 | 168 | force_defaults=False |
|
168 | 169 | ) |
|
169 | 170 | return Response(html) |
|
170 | 171 | |
|
171 | 172 | @LoginRequired() |
|
172 | 173 | @HasPermissionAllDecorator('hg.admin') |
|
173 | 174 | @CSRFRequired() |
|
174 | 175 | @view_config( |
|
175 | 176 | route_name='admin_permissions_object_update', request_method='POST', |
|
176 | 177 | renderer='rhodecode:templates/admin/permissions/permissions.mako') |
|
177 | 178 | def permissions_objects_update(self): |
|
178 | 179 | _ = self.request.translate |
|
179 | 180 | c = self.load_default_context() |
|
180 | 181 | c.active = 'objects' |
|
181 | 182 | |
|
182 | 183 | _form = ObjectPermissionsForm( |
|
184 | self.request.translate, | |
|
183 | 185 | [x[0] for x in c.repo_perms_choices], |
|
184 | 186 | [x[0] for x in c.group_perms_choices], |
|
185 | 187 | [x[0] for x in c.user_group_perms_choices])() |
|
186 | 188 | |
|
187 | 189 | try: |
|
188 | 190 | form_result = _form.to_python(dict(self.request.POST)) |
|
189 | 191 | form_result.update({'perm_user_name': User.DEFAULT_USER}) |
|
190 | 192 | PermissionModel().update_object_permissions(form_result) |
|
191 | 193 | |
|
192 | 194 | Session().commit() |
|
193 | 195 | h.flash(_('Object permissions updated successfully'), |
|
194 | 196 | category='success') |
|
195 | 197 | |
|
196 | 198 | except formencode.Invalid as errors: |
|
197 | 199 | defaults = errors.value |
|
198 | 200 | |
|
199 | 201 | data = render( |
|
200 | 202 | 'rhodecode:templates/admin/permissions/permissions.mako', |
|
201 | 203 | self._get_template_context(c), self.request) |
|
202 | 204 | html = formencode.htmlfill.render( |
|
203 | 205 | data, |
|
204 | 206 | defaults=defaults, |
|
205 | 207 | errors=errors.error_dict or {}, |
|
206 | 208 | prefix_error=False, |
|
207 | 209 | encoding="UTF-8", |
|
208 | 210 | force_defaults=False |
|
209 | 211 | ) |
|
210 | 212 | return Response(html) |
|
211 | 213 | except Exception: |
|
212 | 214 | log.exception("Exception during update of permissions") |
|
213 | 215 | h.flash(_('Error occurred during update of permissions'), |
|
214 | 216 | category='error') |
|
215 | 217 | |
|
216 | 218 | raise HTTPFound(h.route_path('admin_permissions_object')) |
|
217 | 219 | |
|
218 | 220 | @LoginRequired() |
|
219 | 221 | @HasPermissionAllDecorator('hg.admin') |
|
220 | 222 | @view_config( |
|
221 | 223 | route_name='admin_permissions_global', request_method='GET', |
|
222 | 224 | renderer='rhodecode:templates/admin/permissions/permissions.mako') |
|
223 | 225 | def permissions_global(self): |
|
224 | 226 | c = self.load_default_context() |
|
225 | 227 | c.active = 'global' |
|
226 | 228 | |
|
227 | 229 | c.user = User.get_default_user(refresh=True) |
|
228 | 230 | defaults = {} |
|
229 | 231 | defaults.update(c.user.get_default_perms()) |
|
230 | 232 | |
|
231 | 233 | data = render( |
|
232 | 234 | 'rhodecode:templates/admin/permissions/permissions.mako', |
|
233 | 235 | self._get_template_context(c), self.request) |
|
234 | 236 | html = formencode.htmlfill.render( |
|
235 | 237 | data, |
|
236 | 238 | defaults=defaults, |
|
237 | 239 | encoding="UTF-8", |
|
238 | 240 | force_defaults=False |
|
239 | 241 | ) |
|
240 | 242 | return Response(html) |
|
241 | 243 | |
|
242 | 244 | @LoginRequired() |
|
243 | 245 | @HasPermissionAllDecorator('hg.admin') |
|
244 | 246 | @CSRFRequired() |
|
245 | 247 | @view_config( |
|
246 | 248 | route_name='admin_permissions_global_update', request_method='POST', |
|
247 | 249 | renderer='rhodecode:templates/admin/permissions/permissions.mako') |
|
248 | 250 | def permissions_global_update(self): |
|
249 | 251 | _ = self.request.translate |
|
250 | 252 | c = self.load_default_context() |
|
251 | 253 | c.active = 'global' |
|
252 | 254 | |
|
253 | 255 | _form = UserPermissionsForm( |
|
256 | self.request.translate, | |
|
254 | 257 | [x[0] for x in c.repo_create_choices], |
|
255 | 258 | [x[0] for x in c.repo_create_on_write_choices], |
|
256 | 259 | [x[0] for x in c.repo_group_create_choices], |
|
257 | 260 | [x[0] for x in c.user_group_create_choices], |
|
258 | 261 | [x[0] for x in c.fork_choices], |
|
259 | 262 | [x[0] for x in c.inherit_default_permission_choices])() |
|
260 | 263 | |
|
261 | 264 | try: |
|
262 | 265 | form_result = _form.to_python(dict(self.request.POST)) |
|
263 | 266 | form_result.update({'perm_user_name': User.DEFAULT_USER}) |
|
264 | 267 | PermissionModel().update_user_permissions(form_result) |
|
265 | 268 | |
|
266 | 269 | Session().commit() |
|
267 | 270 | h.flash(_('Global permissions updated successfully'), |
|
268 | 271 | category='success') |
|
269 | 272 | |
|
270 | 273 | except formencode.Invalid as errors: |
|
271 | 274 | defaults = errors.value |
|
272 | 275 | |
|
273 | 276 | data = render( |
|
274 | 277 | 'rhodecode:templates/admin/permissions/permissions.mako', |
|
275 | 278 | self._get_template_context(c), self.request) |
|
276 | 279 | html = formencode.htmlfill.render( |
|
277 | 280 | data, |
|
278 | 281 | defaults=defaults, |
|
279 | 282 | errors=errors.error_dict or {}, |
|
280 | 283 | prefix_error=False, |
|
281 | 284 | encoding="UTF-8", |
|
282 | 285 | force_defaults=False |
|
283 | 286 | ) |
|
284 | 287 | return Response(html) |
|
285 | 288 | except Exception: |
|
286 | 289 | log.exception("Exception during update of permissions") |
|
287 | 290 | h.flash(_('Error occurred during update of permissions'), |
|
288 | 291 | category='error') |
|
289 | 292 | |
|
290 | 293 | raise HTTPFound(h.route_path('admin_permissions_global')) |
|
291 | 294 | |
|
292 | 295 | @LoginRequired() |
|
293 | 296 | @HasPermissionAllDecorator('hg.admin') |
|
294 | 297 | @view_config( |
|
295 | 298 | route_name='admin_permissions_ips', request_method='GET', |
|
296 | 299 | renderer='rhodecode:templates/admin/permissions/permissions.mako') |
|
297 | 300 | def permissions_ips(self): |
|
298 | 301 | c = self.load_default_context() |
|
299 | 302 | c.active = 'ips' |
|
300 | 303 | |
|
301 | 304 | c.user = User.get_default_user(refresh=True) |
|
302 | 305 | c.user_ip_map = ( |
|
303 | 306 | UserIpMap.query().filter(UserIpMap.user == c.user).all()) |
|
304 | 307 | |
|
305 | 308 | return self._get_template_context(c) |
|
306 | 309 | |
|
307 | 310 | @LoginRequired() |
|
308 | 311 | @HasPermissionAllDecorator('hg.admin') |
|
309 | 312 | @view_config( |
|
310 | 313 | route_name='admin_permissions_overview', request_method='GET', |
|
311 | 314 | renderer='rhodecode:templates/admin/permissions/permissions.mako') |
|
312 | 315 | def permissions_overview(self): |
|
313 | 316 | c = self.load_default_context() |
|
314 | 317 | c.active = 'perms' |
|
315 | 318 | |
|
316 | 319 | c.user = User.get_default_user(refresh=True) |
|
317 | 320 | c.perm_user = c.user.AuthUser() |
|
318 | 321 | return self._get_template_context(c) |
|
319 | 322 | |
|
320 | 323 | @LoginRequired() |
|
321 | 324 | @HasPermissionAllDecorator('hg.admin') |
|
322 | 325 | @view_config( |
|
323 | 326 | route_name='admin_permissions_auth_token_access', request_method='GET', |
|
324 | 327 | renderer='rhodecode:templates/admin/permissions/permissions.mako') |
|
325 | 328 | def auth_token_access(self): |
|
326 | 329 | from rhodecode import CONFIG |
|
327 | 330 | |
|
328 | 331 | c = self.load_default_context() |
|
329 | 332 | c.active = 'auth_token_access' |
|
330 | 333 | |
|
331 | 334 | c.user = User.get_default_user(refresh=True) |
|
332 | 335 | c.perm_user = c.user.AuthUser() |
|
333 | 336 | |
|
334 | 337 | mapper = self.request.registry.queryUtility(IRoutesMapper) |
|
335 | 338 | c.view_data = [] |
|
336 | 339 | |
|
337 | 340 | _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)') |
|
338 | 341 | introspector = self.request.registry.introspector |
|
339 | 342 | |
|
340 | 343 | view_intr = {} |
|
341 | 344 | for view_data in introspector.get_category('views'): |
|
342 | 345 | intr = view_data['introspectable'] |
|
343 | 346 | |
|
344 | 347 | if 'route_name' in intr and intr['attr']: |
|
345 | 348 | view_intr[intr['route_name']] = '{}:{}'.format( |
|
346 | 349 | str(intr['derived_callable'].func_name), intr['attr'] |
|
347 | 350 | ) |
|
348 | 351 | |
|
349 | 352 | c.whitelist_key = 'api_access_controllers_whitelist' |
|
350 | 353 | c.whitelist_file = CONFIG.get('__file__') |
|
351 | 354 | whitelist_views = aslist( |
|
352 | 355 | CONFIG.get(c.whitelist_key), sep=',') |
|
353 | 356 | |
|
354 | 357 | for route_info in mapper.get_routes(): |
|
355 | 358 | if not route_info.name.startswith('__'): |
|
356 | 359 | routepath = route_info.pattern |
|
357 | 360 | |
|
358 | 361 | def replace(matchobj): |
|
359 | 362 | if matchobj.group(1): |
|
360 | 363 | return "{%s}" % matchobj.group(1).split(':')[0] |
|
361 | 364 | else: |
|
362 | 365 | return "{%s}" % matchobj.group(2) |
|
363 | 366 | |
|
364 | 367 | routepath = _argument_prog.sub(replace, routepath) |
|
365 | 368 | |
|
366 | 369 | if not routepath.startswith('/'): |
|
367 | 370 | routepath = '/' + routepath |
|
368 | 371 | |
|
369 | 372 | view_fqn = view_intr.get(route_info.name, 'NOT AVAILABLE') |
|
370 | 373 | active = view_fqn in whitelist_views |
|
371 | 374 | c.view_data.append((route_info.name, view_fqn, routepath, active)) |
|
372 | 375 | |
|
373 | 376 | c.whitelist_views = whitelist_views |
|
374 | 377 | return self._get_template_context(c) |
|
375 | 378 | |
|
376 | 379 | def ssh_enabled(self): |
|
377 | 380 | return self.request.registry.settings.get( |
|
378 | 381 | 'ssh.generate_authorized_keyfile') |
|
379 | 382 | |
|
380 | 383 | @LoginRequired() |
|
381 | 384 | @HasPermissionAllDecorator('hg.admin') |
|
382 | 385 | @view_config( |
|
383 | 386 | route_name='admin_permissions_ssh_keys', request_method='GET', |
|
384 | 387 | renderer='rhodecode:templates/admin/permissions/permissions.mako') |
|
385 | 388 | def ssh_keys(self): |
|
386 | 389 | c = self.load_default_context() |
|
387 | 390 | c.active = 'ssh_keys' |
|
388 | 391 | c.ssh_enabled = self.ssh_enabled() |
|
389 | 392 | return self._get_template_context(c) |
|
390 | 393 | |
|
391 | 394 | @LoginRequired() |
|
392 | 395 | @HasPermissionAllDecorator('hg.admin') |
|
393 | 396 | @view_config( |
|
394 | 397 | route_name='admin_permissions_ssh_keys_data', request_method='GET', |
|
395 | 398 | renderer='json_ext', xhr=True) |
|
396 | 399 | def ssh_keys_data(self): |
|
397 | 400 | _ = self.request.translate |
|
401 | self.load_default_context() | |
|
398 | 402 | column_map = { |
|
399 | 403 | 'fingerprint': 'ssh_key_fingerprint', |
|
400 | 404 | 'username': User.username |
|
401 | 405 | } |
|
402 | 406 | draw, start, limit = self._extract_chunk(self.request) |
|
403 | 407 | search_q, order_by, order_dir = self._extract_ordering( |
|
404 | 408 | self.request, column_map=column_map) |
|
405 | 409 | |
|
406 | 410 | ssh_keys_data_total_count = UserSshKeys.query()\ |
|
407 | 411 | .count() |
|
408 | 412 | |
|
409 | 413 | # json generate |
|
410 | 414 | base_q = UserSshKeys.query().join(UserSshKeys.user) |
|
411 | 415 | |
|
412 | 416 | if search_q: |
|
413 | 417 | like_expression = u'%{}%'.format(safe_unicode(search_q)) |
|
414 | 418 | base_q = base_q.filter(or_( |
|
415 | 419 | User.username.ilike(like_expression), |
|
416 | 420 | UserSshKeys.ssh_key_fingerprint.ilike(like_expression), |
|
417 | 421 | )) |
|
418 | 422 | |
|
419 | 423 | users_data_total_filtered_count = base_q.count() |
|
420 | 424 | |
|
421 | 425 | sort_col = self._get_order_col(order_by, UserSshKeys) |
|
422 | 426 | if sort_col: |
|
423 | 427 | if order_dir == 'asc': |
|
424 | 428 | # handle null values properly to order by NULL last |
|
425 | 429 | if order_by in ['created_on']: |
|
426 | 430 | sort_col = coalesce(sort_col, datetime.date.max) |
|
427 | 431 | sort_col = sort_col.asc() |
|
428 | 432 | else: |
|
429 | 433 | # handle null values properly to order by NULL last |
|
430 | 434 | if order_by in ['created_on']: |
|
431 | 435 | sort_col = coalesce(sort_col, datetime.date.min) |
|
432 | 436 | sort_col = sort_col.desc() |
|
433 | 437 | |
|
434 | 438 | base_q = base_q.order_by(sort_col) |
|
435 | 439 | base_q = base_q.offset(start).limit(limit) |
|
436 | 440 | |
|
437 | 441 | ssh_keys = base_q.all() |
|
438 | 442 | |
|
439 | 443 | ssh_keys_data = [] |
|
440 | 444 | for ssh_key in ssh_keys: |
|
441 | 445 | ssh_keys_data.append({ |
|
442 | 446 | "username": h.gravatar_with_user(self.request, ssh_key.user.username), |
|
443 | 447 | "fingerprint": ssh_key.ssh_key_fingerprint, |
|
444 | 448 | "description": ssh_key.description, |
|
445 | 449 | "created_on": h.format_date(ssh_key.created_on), |
|
446 | 450 | "accessed_on": h.format_date(ssh_key.accessed_on), |
|
447 | 451 | "action": h.link_to( |
|
448 | 452 | _('Edit'), h.route_path('edit_user_ssh_keys', |
|
449 | 453 | user_id=ssh_key.user.user_id)) |
|
450 | 454 | }) |
|
451 | 455 | |
|
452 | 456 | data = ({ |
|
453 | 457 | 'draw': draw, |
|
454 | 458 | 'data': ssh_keys_data, |
|
455 | 459 | 'recordsTotal': ssh_keys_data_total_count, |
|
456 | 460 | 'recordsFiltered': users_data_total_filtered_count, |
|
457 | 461 | }) |
|
458 | 462 | |
|
459 | 463 | return data |
|
460 | 464 | |
|
461 | 465 | @LoginRequired() |
|
462 | 466 | @HasPermissionAllDecorator('hg.admin') |
|
463 | 467 | @CSRFRequired() |
|
464 | 468 | @view_config( |
|
465 | 469 | route_name='admin_permissions_ssh_keys_update', request_method='POST', |
|
466 | 470 | renderer='rhodecode:templates/admin/permissions/permissions.mako') |
|
467 | 471 | def ssh_keys_update(self): |
|
468 | 472 | _ = self.request.translate |
|
469 | 473 | self.load_default_context() |
|
470 | 474 | |
|
471 | 475 | ssh_enabled = self.ssh_enabled() |
|
472 | 476 | key_file = self.request.registry.settings.get( |
|
473 | 477 | 'ssh.authorized_keys_file_path') |
|
474 | 478 | if ssh_enabled: |
|
475 | 479 | trigger(SshKeyFileChangeEvent(), self.request.registry) |
|
476 | 480 | h.flash(_('Updated SSH keys file: {}').format(key_file), |
|
477 | 481 | category='success') |
|
478 | 482 | else: |
|
479 | 483 | h.flash(_('SSH key support is disabled in .ini file'), |
|
480 | 484 | category='warning') |
|
481 | 485 | |
|
482 | 486 | raise HTTPFound(h.route_path('admin_permissions_ssh_keys')) |
@@ -1,91 +1,91 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | |
|
3 | 3 | # Copyright (C) 2016-2017 RhodeCode GmbH |
|
4 | 4 | # |
|
5 | 5 | # This program is free software: you can redistribute it and/or modify |
|
6 | 6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
7 | 7 | # (only), as published by the Free Software Foundation. |
|
8 | 8 | # |
|
9 | 9 | # This program is distributed in the hope that it will be useful, |
|
10 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 | 12 | # GNU General Public License for more details. |
|
13 | 13 | # |
|
14 | 14 | # You should have received a copy of the GNU Affero General Public License |
|
15 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | 16 | # |
|
17 | 17 | # This program is dual-licensed. If you wish to learn more about the |
|
18 | 18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
19 | 19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
20 | 20 | |
|
21 | 21 | import logging |
|
22 | 22 | |
|
23 | 23 | import psutil |
|
24 | 24 | from pyramid.view import view_config |
|
25 | 25 | |
|
26 | 26 | from rhodecode.apps._base import BaseAppView |
|
27 | 27 | from rhodecode.apps.admin.navigation import navigation_list |
|
28 | 28 | from rhodecode.lib.auth import ( |
|
29 | 29 | LoginRequired, HasPermissionAllDecorator, CSRFRequired) |
|
30 | 30 | from rhodecode.lib.utils2 import safe_int |
|
31 | 31 | |
|
32 | 32 | log = logging.getLogger(__name__) |
|
33 | 33 | |
|
34 | 34 | |
|
35 | 35 | class AdminProcessManagementView(BaseAppView): |
|
36 | 36 | def load_default_context(self): |
|
37 | 37 | c = self._get_local_tmpl_context() |
|
38 | self._register_global_c(c) | |
|
38 | ||
|
39 | 39 | return c |
|
40 | 40 | |
|
41 | 41 | @LoginRequired() |
|
42 | 42 | @HasPermissionAllDecorator('hg.admin') |
|
43 | 43 | @view_config( |
|
44 | 44 | route_name='admin_settings_process_management', request_method='GET', |
|
45 | 45 | renderer='rhodecode:templates/admin/settings/settings.mako') |
|
46 | 46 | def process_management(self): |
|
47 | 47 | _ = self.request.translate |
|
48 | 48 | c = self.load_default_context() |
|
49 | 49 | |
|
50 | 50 | c.active = 'process_management' |
|
51 | 51 | c.navlist = navigation_list(self.request) |
|
52 | 52 | c.gunicorn_processes = ( |
|
53 | 53 | p for p in psutil.process_iter() if 'gunicorn' in p.name()) |
|
54 | 54 | return self._get_template_context(c) |
|
55 | 55 | |
|
56 | 56 | @LoginRequired() |
|
57 | 57 | @HasPermissionAllDecorator('hg.admin') |
|
58 | 58 | @CSRFRequired() |
|
59 | 59 | @view_config( |
|
60 | 60 | route_name='admin_settings_process_management_signal', |
|
61 | 61 | request_method='POST', renderer='json_ext') |
|
62 | 62 | def process_management_signal(self): |
|
63 | 63 | pids = self.request.json.get('pids', []) |
|
64 | 64 | result = [] |
|
65 | 65 | def on_terminate(proc): |
|
66 | 66 | msg = "process `PID:{}` terminated with exit code {}".format( |
|
67 | 67 | proc.pid, proc.returncode) |
|
68 | 68 | result.append(msg) |
|
69 | 69 | |
|
70 | 70 | procs = [] |
|
71 | 71 | for pid in pids: |
|
72 | 72 | pid = safe_int(pid) |
|
73 | 73 | if pid: |
|
74 | 74 | try: |
|
75 | 75 | proc = psutil.Process(pid) |
|
76 | 76 | except psutil.NoSuchProcess: |
|
77 | 77 | continue |
|
78 | 78 | |
|
79 | 79 | children = proc.children(recursive=True) |
|
80 | 80 | if children: |
|
81 | 81 | print('Wont kill Master Process') |
|
82 | 82 | else: |
|
83 | 83 | procs.append(proc) |
|
84 | 84 | |
|
85 | 85 | for p in procs: |
|
86 | 86 | p.terminate() |
|
87 | 87 | gone, alive = psutil.wait_procs(procs, timeout=10, callback=on_terminate) |
|
88 | 88 | for p in alive: |
|
89 | 89 | p.kill() |
|
90 | 90 | |
|
91 | 91 | return {'result': result} |
@@ -1,204 +1,205 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | |
|
3 | 3 | # Copyright (C) 2016-2017 RhodeCode GmbH |
|
4 | 4 | # |
|
5 | 5 | # This program is free software: you can redistribute it and/or modify |
|
6 | 6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
7 | 7 | # (only), as published by the Free Software Foundation. |
|
8 | 8 | # |
|
9 | 9 | # This program is distributed in the hope that it will be useful, |
|
10 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 | 12 | # GNU General Public License for more details. |
|
13 | 13 | # |
|
14 | 14 | # You should have received a copy of the GNU Affero General Public License |
|
15 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | 16 | # |
|
17 | 17 | # This program is dual-licensed. If you wish to learn more about the |
|
18 | 18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
19 | 19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
20 | 20 | |
|
21 | 21 | import logging |
|
22 | 22 | import formencode |
|
23 | 23 | import formencode.htmlfill |
|
24 | 24 | |
|
25 | 25 | from pyramid.httpexceptions import HTTPFound, HTTPForbidden |
|
26 | 26 | from pyramid.view import view_config |
|
27 | 27 | from pyramid.renderers import render |
|
28 | 28 | from pyramid.response import Response |
|
29 | 29 | |
|
30 | 30 | from rhodecode.apps._base import BaseAppView, DataGridAppView |
|
31 | 31 | |
|
32 | 32 | from rhodecode.lib.ext_json import json |
|
33 | 33 | from rhodecode.lib.auth import ( |
|
34 | 34 | LoginRequired, CSRFRequired, NotAnonymous, |
|
35 | 35 | HasPermissionAny, HasRepoGroupPermissionAny) |
|
36 | 36 | from rhodecode.lib import helpers as h, audit_logger |
|
37 | 37 | from rhodecode.lib.utils2 import safe_int, safe_unicode |
|
38 | 38 | from rhodecode.model.forms import RepoGroupForm |
|
39 | 39 | from rhodecode.model.repo_group import RepoGroupModel |
|
40 | 40 | from rhodecode.model.scm import RepoGroupList |
|
41 | 41 | from rhodecode.model.db import Session, RepoGroup |
|
42 | 42 | |
|
43 | 43 | log = logging.getLogger(__name__) |
|
44 | 44 | |
|
45 | 45 | |
|
46 | 46 | class AdminRepoGroupsView(BaseAppView, DataGridAppView): |
|
47 | 47 | |
|
48 | 48 | def load_default_context(self): |
|
49 | 49 | c = self._get_local_tmpl_context() |
|
50 | self._register_global_c(c) | |
|
50 | ||
|
51 | 51 | return c |
|
52 | 52 | |
|
53 | 53 | def _load_form_data(self, c): |
|
54 | 54 | allow_empty_group = False |
|
55 | 55 | |
|
56 | 56 | if self._can_create_repo_group(): |
|
57 | 57 | # we're global admin, we're ok and we can create TOP level groups |
|
58 | 58 | allow_empty_group = True |
|
59 | 59 | |
|
60 | 60 | # override the choices for this form, we need to filter choices |
|
61 | 61 | # and display only those we have ADMIN right |
|
62 | 62 | groups_with_admin_rights = RepoGroupList( |
|
63 | 63 | RepoGroup.query().all(), |
|
64 | 64 | perm_set=['group.admin']) |
|
65 | 65 | c.repo_groups = RepoGroup.groups_choices( |
|
66 | 66 | groups=groups_with_admin_rights, |
|
67 | 67 | show_empty_group=allow_empty_group) |
|
68 | 68 | |
|
69 | 69 | def _can_create_repo_group(self, parent_group_id=None): |
|
70 | 70 | is_admin = HasPermissionAny('hg.admin')('group create controller') |
|
71 | 71 | create_repo_group = HasPermissionAny( |
|
72 | 72 | 'hg.repogroup.create.true')('group create controller') |
|
73 | 73 | if is_admin or (create_repo_group and not parent_group_id): |
|
74 | 74 | # we're global admin, or we have global repo group create |
|
75 | 75 | # permission |
|
76 | 76 | # we're ok and we can create TOP level groups |
|
77 | 77 | return True |
|
78 | 78 | elif parent_group_id: |
|
79 | 79 | # we check the permission if we can write to parent group |
|
80 | 80 | group = RepoGroup.get(parent_group_id) |
|
81 | 81 | group_name = group.group_name if group else None |
|
82 | 82 | if HasRepoGroupPermissionAny('group.admin')( |
|
83 | 83 | group_name, 'check if user is an admin of group'): |
|
84 | 84 | # we're an admin of passed in group, we're ok. |
|
85 | 85 | return True |
|
86 | 86 | else: |
|
87 | 87 | return False |
|
88 | 88 | return False |
|
89 | 89 | |
|
90 | 90 | @LoginRequired() |
|
91 | 91 | @NotAnonymous() |
|
92 | 92 | # perms check inside |
|
93 | 93 | @view_config( |
|
94 | 94 | route_name='repo_groups', request_method='GET', |
|
95 | 95 | renderer='rhodecode:templates/admin/repo_groups/repo_groups.mako') |
|
96 | 96 | def repo_group_list(self): |
|
97 | 97 | c = self.load_default_context() |
|
98 | 98 | |
|
99 | 99 | repo_group_list = RepoGroup.get_all_repo_groups() |
|
100 | 100 | repo_group_list_acl = RepoGroupList( |
|
101 | 101 | repo_group_list, perm_set=['group.admin']) |
|
102 | 102 | repo_group_data = RepoGroupModel().get_repo_groups_as_dict( |
|
103 | 103 | repo_group_list=repo_group_list_acl, admin=True) |
|
104 | 104 | c.data = json.dumps(repo_group_data) |
|
105 | 105 | return self._get_template_context(c) |
|
106 | 106 | |
|
107 | 107 | @LoginRequired() |
|
108 | 108 | @NotAnonymous() |
|
109 | 109 | # perm checks inside |
|
110 | 110 | @view_config( |
|
111 | 111 | route_name='repo_group_new', request_method='GET', |
|
112 | 112 | renderer='rhodecode:templates/admin/repo_groups/repo_group_add.mako') |
|
113 | 113 | def repo_group_new(self): |
|
114 | 114 | c = self.load_default_context() |
|
115 | 115 | |
|
116 | 116 | # perm check for admin, create_group perm or admin of parent_group |
|
117 | 117 | parent_group_id = safe_int(self.request.GET.get('parent_group')) |
|
118 | 118 | if not self._can_create_repo_group(parent_group_id): |
|
119 | 119 | raise HTTPForbidden() |
|
120 | 120 | |
|
121 | 121 | self._load_form_data(c) |
|
122 | 122 | |
|
123 | 123 | defaults = {} # Future proof for default of repo group |
|
124 | 124 | data = render( |
|
125 | 125 | 'rhodecode:templates/admin/repo_groups/repo_group_add.mako', |
|
126 | 126 | self._get_template_context(c), self.request) |
|
127 | 127 | html = formencode.htmlfill.render( |
|
128 | 128 | data, |
|
129 | 129 | defaults=defaults, |
|
130 | 130 | encoding="UTF-8", |
|
131 | 131 | force_defaults=False |
|
132 | 132 | ) |
|
133 | 133 | return Response(html) |
|
134 | 134 | |
|
135 | 135 | @LoginRequired() |
|
136 | 136 | @NotAnonymous() |
|
137 | 137 | @CSRFRequired() |
|
138 | 138 | # perm checks inside |
|
139 | 139 | @view_config( |
|
140 | 140 | route_name='repo_group_create', request_method='POST', |
|
141 | 141 | renderer='rhodecode:templates/admin/repo_groups/repo_group_add.mako') |
|
142 | 142 | def repo_group_create(self): |
|
143 | 143 | c = self.load_default_context() |
|
144 | 144 | _ = self.request.translate |
|
145 | 145 | |
|
146 | 146 | parent_group_id = safe_int(self.request.POST.get('group_parent_id')) |
|
147 | 147 | can_create = self._can_create_repo_group(parent_group_id) |
|
148 | 148 | |
|
149 | 149 | self._load_form_data(c) |
|
150 | 150 | # permissions for can create group based on parent_id are checked |
|
151 | 151 | # here in the Form |
|
152 | 152 | available_groups = map(lambda k: safe_unicode(k[0]), c.repo_groups) |
|
153 |
repo_group_form = RepoGroupForm( |
|
|
153 | repo_group_form = RepoGroupForm( | |
|
154 | self.request.translate, available_groups=available_groups, | |
|
154 | 155 |
|
|
155 | 156 | |
|
156 | 157 | repo_group_name = self.request.POST.get('group_name') |
|
157 | 158 | try: |
|
158 | 159 | owner = self._rhodecode_user |
|
159 | 160 | form_result = repo_group_form.to_python(dict(self.request.POST)) |
|
160 | 161 | repo_group = RepoGroupModel().create( |
|
161 | 162 | group_name=form_result['group_name_full'], |
|
162 | 163 | group_description=form_result['group_description'], |
|
163 | 164 | owner=owner.user_id, |
|
164 | 165 | copy_permissions=form_result['group_copy_permissions'] |
|
165 | 166 | ) |
|
166 | 167 | Session().flush() |
|
167 | 168 | |
|
168 | 169 | repo_group_data = repo_group.get_api_data() |
|
169 | 170 | audit_logger.store_web( |
|
170 | 171 | 'repo_group.create', action_data={'data': repo_group_data}, |
|
171 | 172 | user=self._rhodecode_user) |
|
172 | 173 | |
|
173 | 174 | Session().commit() |
|
174 | 175 | |
|
175 | 176 | _new_group_name = form_result['group_name_full'] |
|
176 | 177 | |
|
177 | 178 | repo_group_url = h.link_to( |
|
178 | 179 | _new_group_name, |
|
179 | 180 | h.route_path('repo_group_home', repo_group_name=_new_group_name)) |
|
180 | 181 | h.flash(h.literal(_('Created repository group %s') |
|
181 | 182 | % repo_group_url), category='success') |
|
182 | 183 | |
|
183 | 184 | except formencode.Invalid as errors: |
|
184 | 185 | data = render( |
|
185 | 186 | 'rhodecode:templates/admin/repo_groups/repo_group_add.mako', |
|
186 | 187 | self._get_template_context(c), self.request) |
|
187 | 188 | html = formencode.htmlfill.render( |
|
188 | 189 | data, |
|
189 | 190 | defaults=errors.value, |
|
190 | 191 | errors=errors.error_dict or {}, |
|
191 | 192 | prefix_error=False, |
|
192 | 193 | encoding="UTF-8", |
|
193 | 194 | force_defaults=False |
|
194 | 195 | ) |
|
195 | 196 | return Response(html) |
|
196 | 197 | except Exception: |
|
197 | 198 | log.exception("Exception during creation of repository group") |
|
198 | 199 | h.flash(_('Error occurred during creation of repository group %s') |
|
199 | 200 | % repo_group_name, category='error') |
|
200 | 201 | raise HTTPFound(h.route_path('home')) |
|
201 | 202 | |
|
202 | 203 | raise HTTPFound( |
|
203 | 204 | h.route_path('repo_group_home', |
|
204 | 205 | repo_group_name=form_result['group_name_full'])) |
@@ -1,182 +1,183 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | |
|
3 | 3 | # Copyright (C) 2016-2017 RhodeCode GmbH |
|
4 | 4 | # |
|
5 | 5 | # This program is free software: you can redistribute it and/or modify |
|
6 | 6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
7 | 7 | # (only), as published by the Free Software Foundation. |
|
8 | 8 | # |
|
9 | 9 | # This program is distributed in the hope that it will be useful, |
|
10 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 | 12 | # GNU General Public License for more details. |
|
13 | 13 | # |
|
14 | 14 | # You should have received a copy of the GNU Affero General Public License |
|
15 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | 16 | # |
|
17 | 17 | # This program is dual-licensed. If you wish to learn more about the |
|
18 | 18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
19 | 19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
20 | 20 | |
|
21 | 21 | import logging |
|
22 | 22 | import formencode |
|
23 | 23 | import formencode.htmlfill |
|
24 | 24 | |
|
25 | 25 | from pyramid.httpexceptions import HTTPFound, HTTPForbidden |
|
26 | 26 | from pyramid.view import view_config |
|
27 | 27 | from pyramid.renderers import render |
|
28 | 28 | from pyramid.response import Response |
|
29 | 29 | |
|
30 | 30 | from rhodecode.apps._base import BaseAppView, DataGridAppView |
|
31 | 31 | |
|
32 | 32 | from rhodecode.lib.ext_json import json |
|
33 | 33 | from rhodecode.lib.auth import ( |
|
34 | 34 | LoginRequired, CSRFRequired, NotAnonymous, |
|
35 | 35 | HasPermissionAny, HasRepoGroupPermissionAny) |
|
36 | 36 | from rhodecode.lib import helpers as h |
|
37 | 37 | from rhodecode.lib.utils import repo_name_slug |
|
38 | 38 | from rhodecode.lib.utils2 import safe_int, safe_unicode |
|
39 | 39 | from rhodecode.model.forms import RepoForm |
|
40 | 40 | from rhodecode.model.repo import RepoModel |
|
41 | 41 | from rhodecode.model.scm import RepoList, RepoGroupList, ScmModel |
|
42 | 42 | from rhodecode.model.settings import SettingsModel |
|
43 | 43 | from rhodecode.model.db import Repository, RepoGroup |
|
44 | 44 | |
|
45 | 45 | log = logging.getLogger(__name__) |
|
46 | 46 | |
|
47 | 47 | |
|
48 | 48 | class AdminReposView(BaseAppView, DataGridAppView): |
|
49 | 49 | |
|
50 | 50 | def load_default_context(self): |
|
51 | 51 | c = self._get_local_tmpl_context() |
|
52 | self._register_global_c(c) | |
|
52 | ||
|
53 | 53 | return c |
|
54 | 54 | |
|
55 | 55 | def _load_form_data(self, c): |
|
56 | 56 | acl_groups = RepoGroupList(RepoGroup.query().all(), |
|
57 | 57 | perm_set=['group.write', 'group.admin']) |
|
58 | 58 | c.repo_groups = RepoGroup.groups_choices(groups=acl_groups) |
|
59 | 59 | c.repo_groups_choices = map(lambda k: safe_unicode(k[0]), c.repo_groups) |
|
60 | 60 | c.landing_revs_choices, c.landing_revs = \ |
|
61 | 61 | ScmModel().get_repo_landing_revs() |
|
62 | 62 | c.personal_repo_group = self._rhodecode_user.personal_repo_group |
|
63 | 63 | |
|
64 | 64 | @LoginRequired() |
|
65 | 65 | @NotAnonymous() |
|
66 | 66 | # perms check inside |
|
67 | 67 | @view_config( |
|
68 | 68 | route_name='repos', request_method='GET', |
|
69 | 69 | renderer='rhodecode:templates/admin/repos/repos.mako') |
|
70 | 70 | def repository_list(self): |
|
71 | 71 | c = self.load_default_context() |
|
72 | 72 | |
|
73 | 73 | repo_list = Repository.get_all_repos() |
|
74 | 74 | c.repo_list = RepoList(repo_list, perm_set=['repository.admin']) |
|
75 | 75 | repos_data = RepoModel().get_repos_as_dict( |
|
76 | 76 | repo_list=c.repo_list, admin=True, super_user_actions=True) |
|
77 | 77 | # json used to render the grid |
|
78 | 78 | c.data = json.dumps(repos_data) |
|
79 | 79 | |
|
80 | 80 | return self._get_template_context(c) |
|
81 | 81 | |
|
82 | 82 | @LoginRequired() |
|
83 | 83 | @NotAnonymous() |
|
84 | 84 | # perms check inside |
|
85 | 85 | @view_config( |
|
86 | 86 | route_name='repo_new', request_method='GET', |
|
87 | 87 | renderer='rhodecode:templates/admin/repos/repo_add.mako') |
|
88 | 88 | def repository_new(self): |
|
89 | 89 | c = self.load_default_context() |
|
90 | 90 | |
|
91 | 91 | new_repo = self.request.GET.get('repo', '') |
|
92 | 92 | parent_group = safe_int(self.request.GET.get('parent_group')) |
|
93 | 93 | _gr = RepoGroup.get(parent_group) |
|
94 | 94 | |
|
95 | 95 | if not HasPermissionAny('hg.admin', 'hg.create.repository')(): |
|
96 | 96 | # you're not super admin nor have global create permissions, |
|
97 | 97 | # but maybe you have at least write permission to a parent group ? |
|
98 | 98 | |
|
99 | 99 | gr_name = _gr.group_name if _gr else None |
|
100 | 100 | # create repositories with write permission on group is set to true |
|
101 | 101 | create_on_write = HasPermissionAny('hg.create.write_on_repogroup.true')() |
|
102 | 102 | group_admin = HasRepoGroupPermissionAny('group.admin')(group_name=gr_name) |
|
103 | 103 | group_write = HasRepoGroupPermissionAny('group.write')(group_name=gr_name) |
|
104 | 104 | if not (group_admin or (group_write and create_on_write)): |
|
105 | 105 | raise HTTPForbidden() |
|
106 | 106 | |
|
107 | 107 | self._load_form_data(c) |
|
108 | 108 | c.new_repo = repo_name_slug(new_repo) |
|
109 | 109 | |
|
110 | 110 | # apply the defaults from defaults page |
|
111 | 111 | defaults = SettingsModel().get_default_repo_settings(strip_prefix=True) |
|
112 | 112 | # set checkbox to autochecked |
|
113 | 113 | defaults['repo_copy_permissions'] = True |
|
114 | 114 | |
|
115 | 115 | parent_group_choice = '-1' |
|
116 | 116 | if not self._rhodecode_user.is_admin and self._rhodecode_user.personal_repo_group: |
|
117 | 117 | parent_group_choice = self._rhodecode_user.personal_repo_group |
|
118 | 118 | |
|
119 | 119 | if parent_group and _gr: |
|
120 | 120 | if parent_group in [x[0] for x in c.repo_groups]: |
|
121 | 121 | parent_group_choice = safe_unicode(parent_group) |
|
122 | 122 | |
|
123 | 123 | defaults.update({'repo_group': parent_group_choice}) |
|
124 | 124 | |
|
125 | 125 | data = render('rhodecode:templates/admin/repos/repo_add.mako', |
|
126 | 126 | self._get_template_context(c), self.request) |
|
127 | 127 | html = formencode.htmlfill.render( |
|
128 | 128 | data, |
|
129 | 129 | defaults=defaults, |
|
130 | 130 | encoding="UTF-8", |
|
131 | 131 | force_defaults=False |
|
132 | 132 | ) |
|
133 | 133 | return Response(html) |
|
134 | 134 | |
|
135 | 135 | @LoginRequired() |
|
136 | 136 | @NotAnonymous() |
|
137 | 137 | @CSRFRequired() |
|
138 | 138 | # perms check inside |
|
139 | 139 | @view_config( |
|
140 | 140 | route_name='repo_create', request_method='POST', |
|
141 | 141 | renderer='rhodecode:templates/admin/repos/repos.mako') |
|
142 | 142 | def repository_create(self): |
|
143 | 143 | c = self.load_default_context() |
|
144 | 144 | |
|
145 | 145 | form_result = {} |
|
146 | 146 | task_id = None |
|
147 | 147 | self._load_form_data(c) |
|
148 | 148 | |
|
149 | 149 | try: |
|
150 | 150 | # CanWriteToGroup validators checks permissions of this POST |
|
151 | form_result = RepoForm(repo_groups=c.repo_groups_choices, | |
|
152 | landing_revs=c.landing_revs_choices)()\ | |
|
153 | .to_python(dict(self.request.POST)) | |
|
151 | form = RepoForm( | |
|
152 | self.request.translate, repo_groups=c.repo_groups_choices, | |
|
153 | landing_revs=c.landing_revs_choices)() | |
|
154 | form_results = form.to_python(dict(self.request.POST)) | |
|
154 | 155 | |
|
155 | 156 | # create is done sometimes async on celery, db transaction |
|
156 | 157 | # management is handled there. |
|
157 | 158 | task = RepoModel().create(form_result, self._rhodecode_user.user_id) |
|
158 | 159 | from celery.result import BaseAsyncResult |
|
159 | 160 | if isinstance(task, BaseAsyncResult): |
|
160 | 161 | task_id = task.task_id |
|
161 | 162 | except formencode.Invalid as errors: |
|
162 | 163 | data = render('rhodecode:templates/admin/repos/repo_add.mako', |
|
163 | 164 | self._get_template_context(c), self.request) |
|
164 | 165 | html = formencode.htmlfill.render( |
|
165 | 166 | data, |
|
166 | 167 | defaults=errors.value, |
|
167 | 168 | errors=errors.error_dict or {}, |
|
168 | 169 | prefix_error=False, |
|
169 | 170 | encoding="UTF-8", |
|
170 | 171 | force_defaults=False |
|
171 | 172 | ) |
|
172 | 173 | return Response(html) |
|
173 | 174 | |
|
174 | 175 | except Exception as e: |
|
175 | 176 | msg = self._log_creation_exception(e, form_result.get('repo_name')) |
|
176 | 177 | h.flash(msg, category='error') |
|
177 | 178 | raise HTTPFound(h.route_path('home')) |
|
178 | 179 | |
|
179 | 180 | raise HTTPFound( |
|
180 | 181 | h.route_path('repo_creating', |
|
181 | 182 | repo_name=form_result['repo_name_full'], |
|
182 | 183 | _query=dict(task_id=task_id))) |
@@ -1,102 +1,102 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | |
|
3 | 3 | # Copyright (C) 2016-2017 RhodeCode GmbH |
|
4 | 4 | # |
|
5 | 5 | # This program is free software: you can redistribute it and/or modify |
|
6 | 6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
7 | 7 | # (only), as published by the Free Software Foundation. |
|
8 | 8 | # |
|
9 | 9 | # This program is distributed in the hope that it will be useful, |
|
10 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 | 12 | # GNU General Public License for more details. |
|
13 | 13 | # |
|
14 | 14 | # You should have received a copy of the GNU Affero General Public License |
|
15 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | 16 | # |
|
17 | 17 | # This program is dual-licensed. If you wish to learn more about the |
|
18 | 18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
19 | 19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
20 | 20 | |
|
21 | 21 | import logging |
|
22 | 22 | |
|
23 | 23 | from pyramid.view import view_config |
|
24 | 24 | from pyramid.httpexceptions import HTTPFound |
|
25 | 25 | |
|
26 | 26 | from rhodecode.apps._base import BaseAppView |
|
27 | 27 | from rhodecode.apps.admin.navigation import navigation_list |
|
28 | 28 | from rhodecode.lib.auth import ( |
|
29 | 29 | LoginRequired, HasPermissionAllDecorator, CSRFRequired) |
|
30 | 30 | from rhodecode.lib.utils2 import safe_int |
|
31 | 31 | from rhodecode.lib import system_info |
|
32 | 32 | from rhodecode.lib import user_sessions |
|
33 | 33 | |
|
34 | 34 | |
|
35 | 35 | log = logging.getLogger(__name__) |
|
36 | 36 | |
|
37 | 37 | |
|
38 | 38 | class AdminSessionSettingsView(BaseAppView): |
|
39 | 39 | def load_default_context(self): |
|
40 | 40 | c = self._get_local_tmpl_context() |
|
41 | 41 | |
|
42 | self._register_global_c(c) | |
|
42 | ||
|
43 | 43 | return c |
|
44 | 44 | |
|
45 | 45 | @LoginRequired() |
|
46 | 46 | @HasPermissionAllDecorator('hg.admin') |
|
47 | 47 | @view_config( |
|
48 | 48 | route_name='admin_settings_sessions', request_method='GET', |
|
49 | 49 | renderer='rhodecode:templates/admin/settings/settings.mako') |
|
50 | 50 | def settings_sessions(self): |
|
51 | 51 | c = self.load_default_context() |
|
52 | 52 | |
|
53 | 53 | c.active = 'sessions' |
|
54 | 54 | c.navlist = navigation_list(self.request) |
|
55 | 55 | |
|
56 | 56 | c.cleanup_older_days = 60 |
|
57 | 57 | older_than_seconds = 60 * 60 * 24 * c.cleanup_older_days |
|
58 | 58 | |
|
59 | 59 | config = system_info.rhodecode_config().get_value()['value']['config'] |
|
60 | 60 | c.session_model = user_sessions.get_session_handler( |
|
61 | 61 | config.get('beaker.session.type', 'memory'))(config) |
|
62 | 62 | |
|
63 | 63 | c.session_conf = c.session_model.config |
|
64 | 64 | c.session_count = c.session_model.get_count() |
|
65 | 65 | c.session_expired_count = c.session_model.get_expired_count( |
|
66 | 66 | older_than_seconds) |
|
67 | 67 | |
|
68 | 68 | return self._get_template_context(c) |
|
69 | 69 | |
|
70 | 70 | @LoginRequired() |
|
71 | 71 | @HasPermissionAllDecorator('hg.admin') |
|
72 | 72 | @CSRFRequired() |
|
73 | 73 | @view_config( |
|
74 | 74 | route_name='admin_settings_sessions_cleanup', request_method='POST') |
|
75 | 75 | def settings_sessions_cleanup(self): |
|
76 | 76 | _ = self.request.translate |
|
77 | 77 | expire_days = safe_int(self.request.params.get('expire_days')) |
|
78 | 78 | |
|
79 | 79 | if expire_days is None: |
|
80 | 80 | expire_days = 60 |
|
81 | 81 | |
|
82 | 82 | older_than_seconds = 60 * 60 * 24 * expire_days |
|
83 | 83 | |
|
84 | 84 | config = system_info.rhodecode_config().get_value()['value']['config'] |
|
85 | 85 | session_model = user_sessions.get_session_handler( |
|
86 | 86 | config.get('beaker.session.type', 'memory'))(config) |
|
87 | 87 | |
|
88 | 88 | try: |
|
89 | 89 | session_model.clean_sessions( |
|
90 | 90 | older_than_seconds=older_than_seconds) |
|
91 | 91 | self.request.session.flash( |
|
92 | 92 | _('Cleaned up old sessions'), queue='success') |
|
93 | 93 | except user_sessions.CleanupCommand as msg: |
|
94 | 94 | self.request.session.flash(msg.message, queue='warning') |
|
95 | 95 | except Exception as e: |
|
96 | 96 | log.exception('Failed session cleanup') |
|
97 | 97 | self.request.session.flash( |
|
98 | 98 | _('Failed to cleanup up old sessions'), queue='error') |
|
99 | 99 | |
|
100 | 100 | redirect_to = self.request.resource_path( |
|
101 | 101 | self.context, route_name='admin_settings_sessions') |
|
102 | 102 | return HTTPFound(redirect_to) |
@@ -1,762 +1,762 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | |
|
3 | 3 | # Copyright (C) 2010-2017 RhodeCode GmbH |
|
4 | 4 | # |
|
5 | 5 | # This program is free software: you can redistribute it and/or modify |
|
6 | 6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
7 | 7 | # (only), as published by the Free Software Foundation. |
|
8 | 8 | # |
|
9 | 9 | # This program is distributed in the hope that it will be useful, |
|
10 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 | 12 | # GNU General Public License for more details. |
|
13 | 13 | # |
|
14 | 14 | # You should have received a copy of the GNU Affero General Public License |
|
15 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | 16 | # |
|
17 | 17 | # This program is dual-licensed. If you wish to learn more about the |
|
18 | 18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
19 | 19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
20 | 20 | |
|
21 | 21 | |
|
22 | 22 | import logging |
|
23 | 23 | import collections |
|
24 | 24 | |
|
25 | 25 | import datetime |
|
26 | 26 | import formencode |
|
27 | 27 | import formencode.htmlfill |
|
28 | 28 | |
|
29 | 29 | import rhodecode |
|
30 | 30 | from pyramid.view import view_config |
|
31 | 31 | from pyramid.httpexceptions import HTTPFound, HTTPNotFound |
|
32 | 32 | from pyramid.renderers import render |
|
33 | 33 | from pyramid.response import Response |
|
34 | 34 | |
|
35 | 35 | from rhodecode.apps._base import BaseAppView |
|
36 | 36 | from rhodecode.apps.admin.navigation import navigation_list |
|
37 | 37 | from rhodecode.apps.svn_support.config_keys import generate_config |
|
38 | 38 | from rhodecode.lib import helpers as h |
|
39 | 39 | from rhodecode.lib.auth import ( |
|
40 | 40 | LoginRequired, HasPermissionAllDecorator, CSRFRequired) |
|
41 | 41 | from rhodecode.lib.celerylib import tasks, run_task |
|
42 | 42 | from rhodecode.lib.utils import repo2db_mapper |
|
43 | 43 | from rhodecode.lib.utils2 import str2bool, safe_unicode, AttributeDict |
|
44 | 44 | from rhodecode.lib.index import searcher_from_config |
|
45 | 45 | |
|
46 | 46 | from rhodecode.model.db import RhodeCodeUi, Repository |
|
47 | 47 | from rhodecode.model.forms import (ApplicationSettingsForm, |
|
48 | 48 | ApplicationUiSettingsForm, ApplicationVisualisationForm, |
|
49 | 49 | LabsSettingsForm, IssueTrackerPatternsForm) |
|
50 | 50 | from rhodecode.model.repo_group import RepoGroupModel |
|
51 | 51 | |
|
52 | 52 | from rhodecode.model.scm import ScmModel |
|
53 | 53 | from rhodecode.model.notification import EmailNotificationModel |
|
54 | 54 | from rhodecode.model.meta import Session |
|
55 | 55 | from rhodecode.model.settings import ( |
|
56 | 56 | IssueTrackerSettingsModel, VcsSettingsModel, SettingNotFound, |
|
57 | 57 | SettingsModel) |
|
58 | 58 | |
|
59 | 59 | |
|
60 | 60 | log = logging.getLogger(__name__) |
|
61 | 61 | |
|
62 | 62 | |
|
63 | 63 | class AdminSettingsView(BaseAppView): |
|
64 | 64 | |
|
65 | 65 | def load_default_context(self): |
|
66 | 66 | c = self._get_local_tmpl_context() |
|
67 | 67 | c.labs_active = str2bool( |
|
68 | 68 | rhodecode.CONFIG.get('labs_settings_active', 'true')) |
|
69 | 69 | c.navlist = navigation_list(self.request) |
|
70 | self._register_global_c(c) | |
|
70 | ||
|
71 | 71 | return c |
|
72 | 72 | |
|
73 | 73 | @classmethod |
|
74 | 74 | def _get_ui_settings(cls): |
|
75 | 75 | ret = RhodeCodeUi.query().all() |
|
76 | 76 | |
|
77 | 77 | if not ret: |
|
78 | 78 | raise Exception('Could not get application ui settings !') |
|
79 | 79 | settings = {} |
|
80 | 80 | for each in ret: |
|
81 | 81 | k = each.ui_key |
|
82 | 82 | v = each.ui_value |
|
83 | 83 | if k == '/': |
|
84 | 84 | k = 'root_path' |
|
85 | 85 | |
|
86 | 86 | if k in ['push_ssl', 'publish', 'enabled']: |
|
87 | 87 | v = str2bool(v) |
|
88 | 88 | |
|
89 | 89 | if k.find('.') != -1: |
|
90 | 90 | k = k.replace('.', '_') |
|
91 | 91 | |
|
92 | 92 | if each.ui_section in ['hooks', 'extensions']: |
|
93 | 93 | v = each.ui_active |
|
94 | 94 | |
|
95 | 95 | settings[each.ui_section + '_' + k] = v |
|
96 | 96 | return settings |
|
97 | 97 | |
|
98 | 98 | @classmethod |
|
99 | 99 | def _form_defaults(cls): |
|
100 | 100 | defaults = SettingsModel().get_all_settings() |
|
101 | 101 | defaults.update(cls._get_ui_settings()) |
|
102 | 102 | |
|
103 | 103 | defaults.update({ |
|
104 | 104 | 'new_svn_branch': '', |
|
105 | 105 | 'new_svn_tag': '', |
|
106 | 106 | }) |
|
107 | 107 | return defaults |
|
108 | 108 | |
|
109 | 109 | @LoginRequired() |
|
110 | 110 | @HasPermissionAllDecorator('hg.admin') |
|
111 | 111 | @view_config( |
|
112 | 112 | route_name='admin_settings_vcs', request_method='GET', |
|
113 | 113 | renderer='rhodecode:templates/admin/settings/settings.mako') |
|
114 | 114 | def settings_vcs(self): |
|
115 | 115 | c = self.load_default_context() |
|
116 | 116 | c.active = 'vcs' |
|
117 | 117 | model = VcsSettingsModel() |
|
118 | 118 | c.svn_branch_patterns = model.get_global_svn_branch_patterns() |
|
119 | 119 | c.svn_tag_patterns = model.get_global_svn_tag_patterns() |
|
120 | 120 | |
|
121 | 121 | settings = self.request.registry.settings |
|
122 | 122 | c.svn_proxy_generate_config = settings[generate_config] |
|
123 | 123 | |
|
124 | 124 | defaults = self._form_defaults() |
|
125 | 125 | |
|
126 | 126 | model.create_largeobjects_dirs_if_needed(defaults['paths_root_path']) |
|
127 | 127 | |
|
128 | 128 | data = render('rhodecode:templates/admin/settings/settings.mako', |
|
129 | 129 | self._get_template_context(c), self.request) |
|
130 | 130 | html = formencode.htmlfill.render( |
|
131 | 131 | data, |
|
132 | 132 | defaults=defaults, |
|
133 | 133 | encoding="UTF-8", |
|
134 | 134 | force_defaults=False |
|
135 | 135 | ) |
|
136 | 136 | return Response(html) |
|
137 | 137 | |
|
138 | 138 | @LoginRequired() |
|
139 | 139 | @HasPermissionAllDecorator('hg.admin') |
|
140 | 140 | @CSRFRequired() |
|
141 | 141 | @view_config( |
|
142 | 142 | route_name='admin_settings_vcs_update', request_method='POST', |
|
143 | 143 | renderer='rhodecode:templates/admin/settings/settings.mako') |
|
144 | 144 | def settings_vcs_update(self): |
|
145 | 145 | _ = self.request.translate |
|
146 | 146 | c = self.load_default_context() |
|
147 | 147 | c.active = 'vcs' |
|
148 | 148 | |
|
149 | 149 | model = VcsSettingsModel() |
|
150 | 150 | c.svn_branch_patterns = model.get_global_svn_branch_patterns() |
|
151 | 151 | c.svn_tag_patterns = model.get_global_svn_tag_patterns() |
|
152 | 152 | |
|
153 | 153 | settings = self.request.registry.settings |
|
154 | 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 | 158 | try: |
|
159 | 159 | form_result = application_form.to_python(dict(self.request.POST)) |
|
160 | 160 | except formencode.Invalid as errors: |
|
161 | 161 | h.flash( |
|
162 | 162 | _("Some form inputs contain invalid data."), |
|
163 | 163 | category='error') |
|
164 | 164 | data = render('rhodecode:templates/admin/settings/settings.mako', |
|
165 | 165 | self._get_template_context(c), self.request) |
|
166 | 166 | html = formencode.htmlfill.render( |
|
167 | 167 | data, |
|
168 | 168 | defaults=errors.value, |
|
169 | 169 | errors=errors.error_dict or {}, |
|
170 | 170 | prefix_error=False, |
|
171 | 171 | encoding="UTF-8", |
|
172 | 172 | force_defaults=False |
|
173 | 173 | ) |
|
174 | 174 | return Response(html) |
|
175 | 175 | |
|
176 | 176 | try: |
|
177 | 177 | if c.visual.allow_repo_location_change: |
|
178 | 178 | model.update_global_path_setting( |
|
179 | 179 | form_result['paths_root_path']) |
|
180 | 180 | |
|
181 | 181 | model.update_global_ssl_setting(form_result['web_push_ssl']) |
|
182 | 182 | model.update_global_hook_settings(form_result) |
|
183 | 183 | |
|
184 | 184 | model.create_or_update_global_svn_settings(form_result) |
|
185 | 185 | model.create_or_update_global_hg_settings(form_result) |
|
186 | 186 | model.create_or_update_global_git_settings(form_result) |
|
187 | 187 | model.create_or_update_global_pr_settings(form_result) |
|
188 | 188 | except Exception: |
|
189 | 189 | log.exception("Exception while updating settings") |
|
190 | 190 | h.flash(_('Error occurred during updating ' |
|
191 | 191 | 'application settings'), category='error') |
|
192 | 192 | else: |
|
193 | 193 | Session().commit() |
|
194 | 194 | h.flash(_('Updated VCS settings'), category='success') |
|
195 | 195 | raise HTTPFound(h.route_path('admin_settings_vcs')) |
|
196 | 196 | |
|
197 | 197 | data = render('rhodecode:templates/admin/settings/settings.mako', |
|
198 | 198 | self._get_template_context(c), self.request) |
|
199 | 199 | html = formencode.htmlfill.render( |
|
200 | 200 | data, |
|
201 | 201 | defaults=self._form_defaults(), |
|
202 | 202 | encoding="UTF-8", |
|
203 | 203 | force_defaults=False |
|
204 | 204 | ) |
|
205 | 205 | return Response(html) |
|
206 | 206 | |
|
207 | 207 | @LoginRequired() |
|
208 | 208 | @HasPermissionAllDecorator('hg.admin') |
|
209 | 209 | @CSRFRequired() |
|
210 | 210 | @view_config( |
|
211 | 211 | route_name='admin_settings_vcs_svn_pattern_delete', request_method='POST', |
|
212 | 212 | renderer='json_ext', xhr=True) |
|
213 | 213 | def settings_vcs_delete_svn_pattern(self): |
|
214 | 214 | delete_pattern_id = self.request.POST.get('delete_svn_pattern') |
|
215 | 215 | model = VcsSettingsModel() |
|
216 | 216 | try: |
|
217 | 217 | model.delete_global_svn_pattern(delete_pattern_id) |
|
218 | 218 | except SettingNotFound: |
|
219 | 219 | log.exception( |
|
220 | 220 | 'Failed to delete svn_pattern with id %s', delete_pattern_id) |
|
221 | 221 | raise HTTPNotFound() |
|
222 | 222 | |
|
223 | 223 | Session().commit() |
|
224 | 224 | return True |
|
225 | 225 | |
|
226 | 226 | @LoginRequired() |
|
227 | 227 | @HasPermissionAllDecorator('hg.admin') |
|
228 | 228 | @view_config( |
|
229 | 229 | route_name='admin_settings_mapping', request_method='GET', |
|
230 | 230 | renderer='rhodecode:templates/admin/settings/settings.mako') |
|
231 | 231 | def settings_mapping(self): |
|
232 | 232 | c = self.load_default_context() |
|
233 | 233 | c.active = 'mapping' |
|
234 | 234 | |
|
235 | 235 | data = render('rhodecode:templates/admin/settings/settings.mako', |
|
236 | 236 | self._get_template_context(c), self.request) |
|
237 | 237 | html = formencode.htmlfill.render( |
|
238 | 238 | data, |
|
239 | 239 | defaults=self._form_defaults(), |
|
240 | 240 | encoding="UTF-8", |
|
241 | 241 | force_defaults=False |
|
242 | 242 | ) |
|
243 | 243 | return Response(html) |
|
244 | 244 | |
|
245 | 245 | @LoginRequired() |
|
246 | 246 | @HasPermissionAllDecorator('hg.admin') |
|
247 | 247 | @CSRFRequired() |
|
248 | 248 | @view_config( |
|
249 | 249 | route_name='admin_settings_mapping_update', request_method='POST', |
|
250 | 250 | renderer='rhodecode:templates/admin/settings/settings.mako') |
|
251 | 251 | def settings_mapping_update(self): |
|
252 | 252 | _ = self.request.translate |
|
253 | 253 | c = self.load_default_context() |
|
254 | 254 | c.active = 'mapping' |
|
255 | 255 | rm_obsolete = self.request.POST.get('destroy', False) |
|
256 | 256 | invalidate_cache = self.request.POST.get('invalidate', False) |
|
257 | 257 | log.debug( |
|
258 | 258 | 'rescanning repo location with destroy obsolete=%s', rm_obsolete) |
|
259 | 259 | |
|
260 | 260 | if invalidate_cache: |
|
261 | 261 | log.debug('invalidating all repositories cache') |
|
262 | 262 | for repo in Repository.get_all(): |
|
263 | 263 | ScmModel().mark_for_invalidation(repo.repo_name, delete=True) |
|
264 | 264 | |
|
265 | 265 | filesystem_repos = ScmModel().repo_scan() |
|
266 | 266 | added, removed = repo2db_mapper(filesystem_repos, rm_obsolete) |
|
267 | 267 | _repr = lambda l: ', '.join(map(safe_unicode, l)) or '-' |
|
268 | 268 | h.flash(_('Repositories successfully ' |
|
269 | 269 | 'rescanned added: %s ; removed: %s') % |
|
270 | 270 | (_repr(added), _repr(removed)), |
|
271 | 271 | category='success') |
|
272 | 272 | raise HTTPFound(h.route_path('admin_settings_mapping')) |
|
273 | 273 | |
|
274 | 274 | @LoginRequired() |
|
275 | 275 | @HasPermissionAllDecorator('hg.admin') |
|
276 | 276 | @view_config( |
|
277 | 277 | route_name='admin_settings', request_method='GET', |
|
278 | 278 | renderer='rhodecode:templates/admin/settings/settings.mako') |
|
279 | 279 | @view_config( |
|
280 | 280 | route_name='admin_settings_global', request_method='GET', |
|
281 | 281 | renderer='rhodecode:templates/admin/settings/settings.mako') |
|
282 | 282 | def settings_global(self): |
|
283 | 283 | c = self.load_default_context() |
|
284 | 284 | c.active = 'global' |
|
285 | 285 | c.personal_repo_group_default_pattern = RepoGroupModel()\ |
|
286 | 286 | .get_personal_group_name_pattern() |
|
287 | 287 | |
|
288 | 288 | data = render('rhodecode:templates/admin/settings/settings.mako', |
|
289 | 289 | self._get_template_context(c), self.request) |
|
290 | 290 | html = formencode.htmlfill.render( |
|
291 | 291 | data, |
|
292 | 292 | defaults=self._form_defaults(), |
|
293 | 293 | encoding="UTF-8", |
|
294 | 294 | force_defaults=False |
|
295 | 295 | ) |
|
296 | 296 | return Response(html) |
|
297 | 297 | |
|
298 | 298 | @LoginRequired() |
|
299 | 299 | @HasPermissionAllDecorator('hg.admin') |
|
300 | 300 | @CSRFRequired() |
|
301 | 301 | @view_config( |
|
302 | 302 | route_name='admin_settings_update', request_method='POST', |
|
303 | 303 | renderer='rhodecode:templates/admin/settings/settings.mako') |
|
304 | 304 | @view_config( |
|
305 | 305 | route_name='admin_settings_global_update', request_method='POST', |
|
306 | 306 | renderer='rhodecode:templates/admin/settings/settings.mako') |
|
307 | 307 | def settings_global_update(self): |
|
308 | 308 | _ = self.request.translate |
|
309 | 309 | c = self.load_default_context() |
|
310 | 310 | c.active = 'global' |
|
311 | 311 | c.personal_repo_group_default_pattern = RepoGroupModel()\ |
|
312 | 312 | .get_personal_group_name_pattern() |
|
313 | application_form = ApplicationSettingsForm()() | |
|
313 | application_form = ApplicationSettingsForm(self.request.translate)() | |
|
314 | 314 | try: |
|
315 | 315 | form_result = application_form.to_python(dict(self.request.POST)) |
|
316 | 316 | except formencode.Invalid as errors: |
|
317 | 317 | data = render('rhodecode:templates/admin/settings/settings.mako', |
|
318 | 318 | self._get_template_context(c), self.request) |
|
319 | 319 | html = formencode.htmlfill.render( |
|
320 | 320 | data, |
|
321 | 321 | defaults=errors.value, |
|
322 | 322 | errors=errors.error_dict or {}, |
|
323 | 323 | prefix_error=False, |
|
324 | 324 | encoding="UTF-8", |
|
325 | 325 | force_defaults=False |
|
326 | 326 | ) |
|
327 | 327 | return Response(html) |
|
328 | 328 | |
|
329 | 329 | settings = [ |
|
330 | 330 | ('title', 'rhodecode_title', 'unicode'), |
|
331 | 331 | ('realm', 'rhodecode_realm', 'unicode'), |
|
332 | 332 | ('pre_code', 'rhodecode_pre_code', 'unicode'), |
|
333 | 333 | ('post_code', 'rhodecode_post_code', 'unicode'), |
|
334 | 334 | ('captcha_public_key', 'rhodecode_captcha_public_key', 'unicode'), |
|
335 | 335 | ('captcha_private_key', 'rhodecode_captcha_private_key', 'unicode'), |
|
336 | 336 | ('create_personal_repo_group', 'rhodecode_create_personal_repo_group', 'bool'), |
|
337 | 337 | ('personal_repo_group_pattern', 'rhodecode_personal_repo_group_pattern', 'unicode'), |
|
338 | 338 | ] |
|
339 | 339 | try: |
|
340 | 340 | for setting, form_key, type_ in settings: |
|
341 | 341 | sett = SettingsModel().create_or_update_setting( |
|
342 | 342 | setting, form_result[form_key], type_) |
|
343 | 343 | Session().add(sett) |
|
344 | 344 | |
|
345 | 345 | Session().commit() |
|
346 | 346 | SettingsModel().invalidate_settings_cache() |
|
347 | 347 | h.flash(_('Updated application settings'), category='success') |
|
348 | 348 | except Exception: |
|
349 | 349 | log.exception("Exception while updating application settings") |
|
350 | 350 | h.flash( |
|
351 | 351 | _('Error occurred during updating application settings'), |
|
352 | 352 | category='error') |
|
353 | 353 | |
|
354 | 354 | raise HTTPFound(h.route_path('admin_settings_global')) |
|
355 | 355 | |
|
356 | 356 | @LoginRequired() |
|
357 | 357 | @HasPermissionAllDecorator('hg.admin') |
|
358 | 358 | @view_config( |
|
359 | 359 | route_name='admin_settings_visual', request_method='GET', |
|
360 | 360 | renderer='rhodecode:templates/admin/settings/settings.mako') |
|
361 | 361 | def settings_visual(self): |
|
362 | 362 | c = self.load_default_context() |
|
363 | 363 | c.active = 'visual' |
|
364 | 364 | |
|
365 | 365 | data = render('rhodecode:templates/admin/settings/settings.mako', |
|
366 | 366 | self._get_template_context(c), self.request) |
|
367 | 367 | html = formencode.htmlfill.render( |
|
368 | 368 | data, |
|
369 | 369 | defaults=self._form_defaults(), |
|
370 | 370 | encoding="UTF-8", |
|
371 | 371 | force_defaults=False |
|
372 | 372 | ) |
|
373 | 373 | return Response(html) |
|
374 | 374 | |
|
375 | 375 | @LoginRequired() |
|
376 | 376 | @HasPermissionAllDecorator('hg.admin') |
|
377 | 377 | @CSRFRequired() |
|
378 | 378 | @view_config( |
|
379 | 379 | route_name='admin_settings_visual_update', request_method='POST', |
|
380 | 380 | renderer='rhodecode:templates/admin/settings/settings.mako') |
|
381 | 381 | def settings_visual_update(self): |
|
382 | 382 | _ = self.request.translate |
|
383 | 383 | c = self.load_default_context() |
|
384 | 384 | c.active = 'visual' |
|
385 | application_form = ApplicationVisualisationForm()() | |
|
385 | application_form = ApplicationVisualisationForm(self.request.translate)() | |
|
386 | 386 | try: |
|
387 | 387 | form_result = application_form.to_python(dict(self.request.POST)) |
|
388 | 388 | except formencode.Invalid as errors: |
|
389 | 389 | data = render('rhodecode:templates/admin/settings/settings.mako', |
|
390 | 390 | self._get_template_context(c), self.request) |
|
391 | 391 | html = formencode.htmlfill.render( |
|
392 | 392 | data, |
|
393 | 393 | defaults=errors.value, |
|
394 | 394 | errors=errors.error_dict or {}, |
|
395 | 395 | prefix_error=False, |
|
396 | 396 | encoding="UTF-8", |
|
397 | 397 | force_defaults=False |
|
398 | 398 | ) |
|
399 | 399 | return Response(html) |
|
400 | 400 | |
|
401 | 401 | try: |
|
402 | 402 | settings = [ |
|
403 | 403 | ('show_public_icon', 'rhodecode_show_public_icon', 'bool'), |
|
404 | 404 | ('show_private_icon', 'rhodecode_show_private_icon', 'bool'), |
|
405 | 405 | ('stylify_metatags', 'rhodecode_stylify_metatags', 'bool'), |
|
406 | 406 | ('repository_fields', 'rhodecode_repository_fields', 'bool'), |
|
407 | 407 | ('dashboard_items', 'rhodecode_dashboard_items', 'int'), |
|
408 | 408 | ('admin_grid_items', 'rhodecode_admin_grid_items', 'int'), |
|
409 | 409 | ('show_version', 'rhodecode_show_version', 'bool'), |
|
410 | 410 | ('use_gravatar', 'rhodecode_use_gravatar', 'bool'), |
|
411 | 411 | ('markup_renderer', 'rhodecode_markup_renderer', 'unicode'), |
|
412 | 412 | ('gravatar_url', 'rhodecode_gravatar_url', 'unicode'), |
|
413 | 413 | ('clone_uri_tmpl', 'rhodecode_clone_uri_tmpl', 'unicode'), |
|
414 | 414 | ('support_url', 'rhodecode_support_url', 'unicode'), |
|
415 | 415 | ('show_revision_number', 'rhodecode_show_revision_number', 'bool'), |
|
416 | 416 | ('show_sha_length', 'rhodecode_show_sha_length', 'int'), |
|
417 | 417 | ] |
|
418 | 418 | for setting, form_key, type_ in settings: |
|
419 | 419 | sett = SettingsModel().create_or_update_setting( |
|
420 | 420 | setting, form_result[form_key], type_) |
|
421 | 421 | Session().add(sett) |
|
422 | 422 | |
|
423 | 423 | Session().commit() |
|
424 | 424 | SettingsModel().invalidate_settings_cache() |
|
425 | 425 | h.flash(_('Updated visualisation settings'), category='success') |
|
426 | 426 | except Exception: |
|
427 | 427 | log.exception("Exception updating visualization settings") |
|
428 | 428 | h.flash(_('Error occurred during updating ' |
|
429 | 429 | 'visualisation settings'), |
|
430 | 430 | category='error') |
|
431 | 431 | |
|
432 | 432 | raise HTTPFound(h.route_path('admin_settings_visual')) |
|
433 | 433 | |
|
434 | 434 | @LoginRequired() |
|
435 | 435 | @HasPermissionAllDecorator('hg.admin') |
|
436 | 436 | @view_config( |
|
437 | 437 | route_name='admin_settings_issuetracker', request_method='GET', |
|
438 | 438 | renderer='rhodecode:templates/admin/settings/settings.mako') |
|
439 | 439 | def settings_issuetracker(self): |
|
440 | 440 | c = self.load_default_context() |
|
441 | 441 | c.active = 'issuetracker' |
|
442 | 442 | defaults = SettingsModel().get_all_settings() |
|
443 | 443 | |
|
444 | 444 | entry_key = 'rhodecode_issuetracker_pat_' |
|
445 | 445 | |
|
446 | 446 | c.issuetracker_entries = {} |
|
447 | 447 | for k, v in defaults.items(): |
|
448 | 448 | if k.startswith(entry_key): |
|
449 | 449 | uid = k[len(entry_key):] |
|
450 | 450 | c.issuetracker_entries[uid] = None |
|
451 | 451 | |
|
452 | 452 | for uid in c.issuetracker_entries: |
|
453 | 453 | c.issuetracker_entries[uid] = AttributeDict({ |
|
454 | 454 | 'pat': defaults.get('rhodecode_issuetracker_pat_' + uid), |
|
455 | 455 | 'url': defaults.get('rhodecode_issuetracker_url_' + uid), |
|
456 | 456 | 'pref': defaults.get('rhodecode_issuetracker_pref_' + uid), |
|
457 | 457 | 'desc': defaults.get('rhodecode_issuetracker_desc_' + uid), |
|
458 | 458 | }) |
|
459 | 459 | |
|
460 | 460 | return self._get_template_context(c) |
|
461 | 461 | |
|
462 | 462 | @LoginRequired() |
|
463 | 463 | @HasPermissionAllDecorator('hg.admin') |
|
464 | 464 | @CSRFRequired() |
|
465 | 465 | @view_config( |
|
466 | 466 | route_name='admin_settings_issuetracker_test', request_method='POST', |
|
467 | 467 | renderer='string', xhr=True) |
|
468 | 468 | def settings_issuetracker_test(self): |
|
469 | 469 | return h.urlify_commit_message( |
|
470 | 470 | self.request.POST.get('test_text', ''), |
|
471 | 471 | 'repo_group/test_repo1') |
|
472 | 472 | |
|
473 | 473 | @LoginRequired() |
|
474 | 474 | @HasPermissionAllDecorator('hg.admin') |
|
475 | 475 | @CSRFRequired() |
|
476 | 476 | @view_config( |
|
477 | 477 | route_name='admin_settings_issuetracker_update', request_method='POST', |
|
478 | 478 | renderer='rhodecode:templates/admin/settings/settings.mako') |
|
479 | 479 | def settings_issuetracker_update(self): |
|
480 | 480 | _ = self.request.translate |
|
481 | 481 | self.load_default_context() |
|
482 | 482 | settings_model = IssueTrackerSettingsModel() |
|
483 | 483 | |
|
484 | 484 | try: |
|
485 | form = IssueTrackerPatternsForm()().to_python(self.request.POST) | |
|
485 | form = IssueTrackerPatternsForm(self.request.translate)().to_python(self.request.POST) | |
|
486 | 486 | except formencode.Invalid as errors: |
|
487 | 487 | log.exception('Failed to add new pattern') |
|
488 | 488 | error = errors |
|
489 | 489 | h.flash(_('Invalid issue tracker pattern: {}'.format(error)), |
|
490 | 490 | category='error') |
|
491 | 491 | raise HTTPFound(h.route_path('admin_settings_issuetracker')) |
|
492 | 492 | |
|
493 | 493 | if form: |
|
494 | 494 | for uid in form.get('delete_patterns', []): |
|
495 | 495 | settings_model.delete_entries(uid) |
|
496 | 496 | |
|
497 | 497 | for pattern in form.get('patterns', []): |
|
498 | 498 | for setting, value, type_ in pattern: |
|
499 | 499 | sett = settings_model.create_or_update_setting( |
|
500 | 500 | setting, value, type_) |
|
501 | 501 | Session().add(sett) |
|
502 | 502 | |
|
503 | 503 | Session().commit() |
|
504 | 504 | |
|
505 | 505 | SettingsModel().invalidate_settings_cache() |
|
506 | 506 | h.flash(_('Updated issue tracker entries'), category='success') |
|
507 | 507 | raise HTTPFound(h.route_path('admin_settings_issuetracker')) |
|
508 | 508 | |
|
509 | 509 | @LoginRequired() |
|
510 | 510 | @HasPermissionAllDecorator('hg.admin') |
|
511 | 511 | @CSRFRequired() |
|
512 | 512 | @view_config( |
|
513 | 513 | route_name='admin_settings_issuetracker_delete', request_method='POST', |
|
514 | 514 | renderer='rhodecode:templates/admin/settings/settings.mako') |
|
515 | 515 | def settings_issuetracker_delete(self): |
|
516 | 516 | _ = self.request.translate |
|
517 | 517 | self.load_default_context() |
|
518 | 518 | uid = self.request.POST.get('uid') |
|
519 | 519 | try: |
|
520 | 520 | IssueTrackerSettingsModel().delete_entries(uid) |
|
521 | 521 | except Exception: |
|
522 | 522 | log.exception('Failed to delete issue tracker setting %s', uid) |
|
523 | 523 | raise HTTPNotFound() |
|
524 | 524 | h.flash(_('Removed issue tracker entry'), category='success') |
|
525 | 525 | raise HTTPFound(h.route_path('admin_settings_issuetracker')) |
|
526 | 526 | |
|
527 | 527 | @LoginRequired() |
|
528 | 528 | @HasPermissionAllDecorator('hg.admin') |
|
529 | 529 | @view_config( |
|
530 | 530 | route_name='admin_settings_email', request_method='GET', |
|
531 | 531 | renderer='rhodecode:templates/admin/settings/settings.mako') |
|
532 | 532 | def settings_email(self): |
|
533 | 533 | c = self.load_default_context() |
|
534 | 534 | c.active = 'email' |
|
535 | 535 | c.rhodecode_ini = rhodecode.CONFIG |
|
536 | 536 | |
|
537 | 537 | data = render('rhodecode:templates/admin/settings/settings.mako', |
|
538 | 538 | self._get_template_context(c), self.request) |
|
539 | 539 | html = formencode.htmlfill.render( |
|
540 | 540 | data, |
|
541 | 541 | defaults=self._form_defaults(), |
|
542 | 542 | encoding="UTF-8", |
|
543 | 543 | force_defaults=False |
|
544 | 544 | ) |
|
545 | 545 | return Response(html) |
|
546 | 546 | |
|
547 | 547 | @LoginRequired() |
|
548 | 548 | @HasPermissionAllDecorator('hg.admin') |
|
549 | 549 | @CSRFRequired() |
|
550 | 550 | @view_config( |
|
551 | 551 | route_name='admin_settings_email_update', request_method='POST', |
|
552 | 552 | renderer='rhodecode:templates/admin/settings/settings.mako') |
|
553 | 553 | def settings_email_update(self): |
|
554 | 554 | _ = self.request.translate |
|
555 | 555 | c = self.load_default_context() |
|
556 | 556 | c.active = 'email' |
|
557 | 557 | |
|
558 | 558 | test_email = self.request.POST.get('test_email') |
|
559 | 559 | |
|
560 | 560 | if not test_email: |
|
561 | 561 | h.flash(_('Please enter email address'), category='error') |
|
562 | 562 | raise HTTPFound(h.route_path('admin_settings_email')) |
|
563 | 563 | |
|
564 | 564 | email_kwargs = { |
|
565 | 565 | 'date': datetime.datetime.now(), |
|
566 | 566 | 'user': c.rhodecode_user, |
|
567 | 567 | 'rhodecode_version': c.rhodecode_version |
|
568 | 568 | } |
|
569 | 569 | |
|
570 | 570 | (subject, headers, email_body, |
|
571 | 571 | email_body_plaintext) = EmailNotificationModel().render_email( |
|
572 | 572 | EmailNotificationModel.TYPE_EMAIL_TEST, **email_kwargs) |
|
573 | 573 | |
|
574 | 574 | recipients = [test_email] if test_email else None |
|
575 | 575 | |
|
576 | 576 | run_task(tasks.send_email, recipients, subject, |
|
577 | 577 | email_body_plaintext, email_body) |
|
578 | 578 | |
|
579 | 579 | h.flash(_('Send email task created'), category='success') |
|
580 | 580 | raise HTTPFound(h.route_path('admin_settings_email')) |
|
581 | 581 | |
|
582 | 582 | @LoginRequired() |
|
583 | 583 | @HasPermissionAllDecorator('hg.admin') |
|
584 | 584 | @view_config( |
|
585 | 585 | route_name='admin_settings_hooks', request_method='GET', |
|
586 | 586 | renderer='rhodecode:templates/admin/settings/settings.mako') |
|
587 | 587 | def settings_hooks(self): |
|
588 | 588 | c = self.load_default_context() |
|
589 | 589 | c.active = 'hooks' |
|
590 | 590 | |
|
591 | 591 | model = SettingsModel() |
|
592 | 592 | c.hooks = model.get_builtin_hooks() |
|
593 | 593 | c.custom_hooks = model.get_custom_hooks() |
|
594 | 594 | |
|
595 | 595 | data = render('rhodecode:templates/admin/settings/settings.mako', |
|
596 | 596 | self._get_template_context(c), self.request) |
|
597 | 597 | html = formencode.htmlfill.render( |
|
598 | 598 | data, |
|
599 | 599 | defaults=self._form_defaults(), |
|
600 | 600 | encoding="UTF-8", |
|
601 | 601 | force_defaults=False |
|
602 | 602 | ) |
|
603 | 603 | return Response(html) |
|
604 | 604 | |
|
605 | 605 | @LoginRequired() |
|
606 | 606 | @HasPermissionAllDecorator('hg.admin') |
|
607 | 607 | @CSRFRequired() |
|
608 | 608 | @view_config( |
|
609 | 609 | route_name='admin_settings_hooks_update', request_method='POST', |
|
610 | 610 | renderer='rhodecode:templates/admin/settings/settings.mako') |
|
611 | 611 | @view_config( |
|
612 | 612 | route_name='admin_settings_hooks_delete', request_method='POST', |
|
613 | 613 | renderer='rhodecode:templates/admin/settings/settings.mako') |
|
614 | 614 | def settings_hooks_update(self): |
|
615 | 615 | _ = self.request.translate |
|
616 | 616 | c = self.load_default_context() |
|
617 | 617 | c.active = 'hooks' |
|
618 | 618 | if c.visual.allow_custom_hooks_settings: |
|
619 | 619 | ui_key = self.request.POST.get('new_hook_ui_key') |
|
620 | 620 | ui_value = self.request.POST.get('new_hook_ui_value') |
|
621 | 621 | |
|
622 | 622 | hook_id = self.request.POST.get('hook_id') |
|
623 | 623 | new_hook = False |
|
624 | 624 | |
|
625 | 625 | model = SettingsModel() |
|
626 | 626 | try: |
|
627 | 627 | if ui_value and ui_key: |
|
628 | 628 | model.create_or_update_hook(ui_key, ui_value) |
|
629 | 629 | h.flash(_('Added new hook'), category='success') |
|
630 | 630 | new_hook = True |
|
631 | 631 | elif hook_id: |
|
632 | 632 | RhodeCodeUi.delete(hook_id) |
|
633 | 633 | Session().commit() |
|
634 | 634 | |
|
635 | 635 | # check for edits |
|
636 | 636 | update = False |
|
637 | 637 | _d = self.request.POST.dict_of_lists() |
|
638 | 638 | for k, v in zip(_d.get('hook_ui_key', []), |
|
639 | 639 | _d.get('hook_ui_value_new', [])): |
|
640 | 640 | model.create_or_update_hook(k, v) |
|
641 | 641 | update = True |
|
642 | 642 | |
|
643 | 643 | if update and not new_hook: |
|
644 | 644 | h.flash(_('Updated hooks'), category='success') |
|
645 | 645 | Session().commit() |
|
646 | 646 | except Exception: |
|
647 | 647 | log.exception("Exception during hook creation") |
|
648 | 648 | h.flash(_('Error occurred during hook creation'), |
|
649 | 649 | category='error') |
|
650 | 650 | |
|
651 | 651 | raise HTTPFound(h.route_path('admin_settings_hooks')) |
|
652 | 652 | |
|
653 | 653 | @LoginRequired() |
|
654 | 654 | @HasPermissionAllDecorator('hg.admin') |
|
655 | 655 | @view_config( |
|
656 | 656 | route_name='admin_settings_search', request_method='GET', |
|
657 | 657 | renderer='rhodecode:templates/admin/settings/settings.mako') |
|
658 | 658 | def settings_search(self): |
|
659 | 659 | c = self.load_default_context() |
|
660 | 660 | c.active = 'search' |
|
661 | 661 | |
|
662 | 662 | searcher = searcher_from_config(self.request.registry.settings) |
|
663 | 663 | c.statistics = searcher.statistics() |
|
664 | 664 | |
|
665 | 665 | return self._get_template_context(c) |
|
666 | 666 | |
|
667 | 667 | @LoginRequired() |
|
668 | 668 | @HasPermissionAllDecorator('hg.admin') |
|
669 | 669 | @view_config( |
|
670 | 670 | route_name='admin_settings_labs', request_method='GET', |
|
671 | 671 | renderer='rhodecode:templates/admin/settings/settings.mako') |
|
672 | 672 | def settings_labs(self): |
|
673 | 673 | c = self.load_default_context() |
|
674 | 674 | if not c.labs_active: |
|
675 | 675 | raise HTTPFound(h.route_path('admin_settings')) |
|
676 | 676 | |
|
677 | 677 | c.active = 'labs' |
|
678 | 678 | c.lab_settings = _LAB_SETTINGS |
|
679 | 679 | |
|
680 | 680 | data = render('rhodecode:templates/admin/settings/settings.mako', |
|
681 | 681 | self._get_template_context(c), self.request) |
|
682 | 682 | html = formencode.htmlfill.render( |
|
683 | 683 | data, |
|
684 | 684 | defaults=self._form_defaults(), |
|
685 | 685 | encoding="UTF-8", |
|
686 | 686 | force_defaults=False |
|
687 | 687 | ) |
|
688 | 688 | return Response(html) |
|
689 | 689 | |
|
690 | 690 | @LoginRequired() |
|
691 | 691 | @HasPermissionAllDecorator('hg.admin') |
|
692 | 692 | @CSRFRequired() |
|
693 | 693 | @view_config( |
|
694 | 694 | route_name='admin_settings_labs_update', request_method='POST', |
|
695 | 695 | renderer='rhodecode:templates/admin/settings/settings.mako') |
|
696 | 696 | def settings_labs_update(self): |
|
697 | 697 | _ = self.request.translate |
|
698 | 698 | c = self.load_default_context() |
|
699 | 699 | c.active = 'labs' |
|
700 | 700 | |
|
701 | application_form = LabsSettingsForm()() | |
|
701 | application_form = LabsSettingsForm(self.request.translate)() | |
|
702 | 702 | try: |
|
703 | 703 | form_result = application_form.to_python(dict(self.request.POST)) |
|
704 | 704 | except formencode.Invalid as errors: |
|
705 | 705 | h.flash( |
|
706 | 706 | _('Some form inputs contain invalid data.'), |
|
707 | 707 | category='error') |
|
708 | 708 | data = render('rhodecode:templates/admin/settings/settings.mako', |
|
709 | 709 | self._get_template_context(c), self.request) |
|
710 | 710 | html = formencode.htmlfill.render( |
|
711 | 711 | data, |
|
712 | 712 | defaults=errors.value, |
|
713 | 713 | errors=errors.error_dict or {}, |
|
714 | 714 | prefix_error=False, |
|
715 | 715 | encoding="UTF-8", |
|
716 | 716 | force_defaults=False |
|
717 | 717 | ) |
|
718 | 718 | return Response(html) |
|
719 | 719 | |
|
720 | 720 | try: |
|
721 | 721 | session = Session() |
|
722 | 722 | for setting in _LAB_SETTINGS: |
|
723 | 723 | setting_name = setting.key[len('rhodecode_'):] |
|
724 | 724 | sett = SettingsModel().create_or_update_setting( |
|
725 | 725 | setting_name, form_result[setting.key], setting.type) |
|
726 | 726 | session.add(sett) |
|
727 | 727 | |
|
728 | 728 | except Exception: |
|
729 | 729 | log.exception('Exception while updating lab settings') |
|
730 | 730 | h.flash(_('Error occurred during updating labs settings'), |
|
731 | 731 | category='error') |
|
732 | 732 | else: |
|
733 | 733 | Session().commit() |
|
734 | 734 | SettingsModel().invalidate_settings_cache() |
|
735 | 735 | h.flash(_('Updated Labs settings'), category='success') |
|
736 | 736 | raise HTTPFound(h.route_path('admin_settings_labs')) |
|
737 | 737 | |
|
738 | 738 | data = render('rhodecode:templates/admin/settings/settings.mako', |
|
739 | 739 | self._get_template_context(c), self.request) |
|
740 | 740 | html = formencode.htmlfill.render( |
|
741 | 741 | data, |
|
742 | 742 | defaults=self._form_defaults(), |
|
743 | 743 | encoding="UTF-8", |
|
744 | 744 | force_defaults=False |
|
745 | 745 | ) |
|
746 | 746 | return Response(html) |
|
747 | 747 | |
|
748 | 748 | |
|
749 | 749 | # :param key: name of the setting including the 'rhodecode_' prefix |
|
750 | 750 | # :param type: the RhodeCodeSetting type to use. |
|
751 | 751 | # :param group: the i18ned group in which we should dispaly this setting |
|
752 | 752 | # :param label: the i18ned label we should display for this setting |
|
753 | 753 | # :param help: the i18ned help we should dispaly for this setting |
|
754 | 754 | LabSetting = collections.namedtuple( |
|
755 | 755 | 'LabSetting', ('key', 'type', 'group', 'label', 'help')) |
|
756 | 756 | |
|
757 | 757 | |
|
758 | 758 | # This list has to be kept in sync with the form |
|
759 | 759 | # rhodecode.model.forms.LabsSettingsForm. |
|
760 | 760 | _LAB_SETTINGS = [ |
|
761 | 761 | |
|
762 | 762 | ] |
@@ -1,209 +1,209 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | |
|
3 | 3 | # Copyright (C) 2016-2017 RhodeCode GmbH |
|
4 | 4 | # |
|
5 | 5 | # This program is free software: you can redistribute it and/or modify |
|
6 | 6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
7 | 7 | # (only), as published by the Free Software Foundation. |
|
8 | 8 | # |
|
9 | 9 | # This program is distributed in the hope that it will be useful, |
|
10 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 | 12 | # GNU General Public License for more details. |
|
13 | 13 | # |
|
14 | 14 | # You should have received a copy of the GNU Affero General Public License |
|
15 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | 16 | # |
|
17 | 17 | # This program is dual-licensed. If you wish to learn more about the |
|
18 | 18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
19 | 19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
20 | 20 | |
|
21 | 21 | import logging |
|
22 | 22 | import urllib2 |
|
23 | 23 | import packaging.version |
|
24 | 24 | |
|
25 | 25 | from pyramid.view import view_config |
|
26 | 26 | |
|
27 | 27 | import rhodecode |
|
28 | 28 | from rhodecode.apps._base import BaseAppView |
|
29 | 29 | from rhodecode.apps.admin.navigation import navigation_list |
|
30 | 30 | from rhodecode.lib import helpers as h |
|
31 | 31 | from rhodecode.lib.auth import (LoginRequired, HasPermissionAllDecorator) |
|
32 | 32 | from rhodecode.lib.utils2 import str2bool |
|
33 | 33 | from rhodecode.lib import system_info |
|
34 | 34 | from rhodecode.lib.ext_json import json |
|
35 | 35 | from rhodecode.model.settings import SettingsModel |
|
36 | 36 | |
|
37 | 37 | log = logging.getLogger(__name__) |
|
38 | 38 | |
|
39 | 39 | |
|
40 | 40 | class AdminSystemInfoSettingsView(BaseAppView): |
|
41 | 41 | def load_default_context(self): |
|
42 | 42 | c = self._get_local_tmpl_context() |
|
43 | self._register_global_c(c) | |
|
43 | ||
|
44 | 44 | return c |
|
45 | 45 | |
|
46 | 46 | @staticmethod |
|
47 | 47 | def get_update_data(update_url): |
|
48 | 48 | """Return the JSON update data.""" |
|
49 | 49 | ver = rhodecode.__version__ |
|
50 | 50 | log.debug('Checking for upgrade on `%s` server', update_url) |
|
51 | 51 | opener = urllib2.build_opener() |
|
52 | 52 | opener.addheaders = [('User-agent', 'RhodeCode-SCM/%s' % ver)] |
|
53 | 53 | response = opener.open(update_url) |
|
54 | 54 | response_data = response.read() |
|
55 | 55 | data = json.loads(response_data) |
|
56 | 56 | |
|
57 | 57 | return data |
|
58 | 58 | |
|
59 | 59 | def get_update_url(self): |
|
60 | 60 | settings = SettingsModel().get_all_settings() |
|
61 | 61 | return settings.get('rhodecode_update_url') |
|
62 | 62 | |
|
63 | 63 | @LoginRequired() |
|
64 | 64 | @HasPermissionAllDecorator('hg.admin') |
|
65 | 65 | @view_config( |
|
66 | 66 | route_name='admin_settings_system', request_method='GET', |
|
67 | 67 | renderer='rhodecode:templates/admin/settings/settings.mako') |
|
68 | 68 | def settings_system_info(self): |
|
69 | 69 | _ = self.request.translate |
|
70 | 70 | c = self.load_default_context() |
|
71 | 71 | |
|
72 | 72 | c.active = 'system' |
|
73 | 73 | c.navlist = navigation_list(self.request) |
|
74 | 74 | |
|
75 | 75 | # TODO(marcink), figure out how to allow only selected users to do this |
|
76 | 76 | c.allowed_to_snapshot = self._rhodecode_user.admin |
|
77 | 77 | |
|
78 | 78 | snapshot = str2bool(self.request.params.get('snapshot')) |
|
79 | 79 | |
|
80 | 80 | c.rhodecode_update_url = self.get_update_url() |
|
81 | 81 | server_info = system_info.get_system_info(self.request.environ) |
|
82 | 82 | |
|
83 | 83 | for key, val in server_info.items(): |
|
84 | 84 | setattr(c, key, val) |
|
85 | 85 | |
|
86 | 86 | def val(name, subkey='human_value'): |
|
87 | 87 | return server_info[name][subkey] |
|
88 | 88 | |
|
89 | 89 | def state(name): |
|
90 | 90 | return server_info[name]['state'] |
|
91 | 91 | |
|
92 | 92 | def val2(name): |
|
93 | 93 | val = server_info[name]['human_value'] |
|
94 | 94 | state = server_info[name]['state'] |
|
95 | 95 | return val, state |
|
96 | 96 | |
|
97 | 97 | update_info_msg = _('Note: please make sure this server can ' |
|
98 | 98 | 'access `${url}` for the update link to work', |
|
99 | 99 | mapping=dict(url=c.rhodecode_update_url)) |
|
100 | 100 | c.data_items = [ |
|
101 | 101 | # update info |
|
102 | 102 | (_('Update info'), h.literal( |
|
103 | 103 | '<span class="link" id="check_for_update" >%s.</span>' % ( |
|
104 | 104 | _('Check for updates')) + |
|
105 | 105 | '<br/> <span >%s.</span>' % (update_info_msg) |
|
106 | 106 | ), ''), |
|
107 | 107 | |
|
108 | 108 | # RhodeCode specific |
|
109 | 109 | (_('RhodeCode Version'), val('rhodecode_app')['text'], state('rhodecode_app')), |
|
110 | 110 | (_('RhodeCode Server IP'), val('server')['server_ip'], state('server')), |
|
111 | 111 | (_('RhodeCode Server ID'), val('server')['server_id'], state('server')), |
|
112 | 112 | (_('RhodeCode Configuration'), val('rhodecode_config')['path'], state('rhodecode_config')), |
|
113 | 113 | (_('RhodeCode Certificate'), val('rhodecode_config')['cert_path'], state('rhodecode_config')), |
|
114 | 114 | (_('Workers'), val('rhodecode_config')['config']['server:main'].get('workers', '?'), state('rhodecode_config')), |
|
115 | 115 | (_('Worker Type'), val('rhodecode_config')['config']['server:main'].get('worker_class', 'sync'), state('rhodecode_config')), |
|
116 | 116 | ('', '', ''), # spacer |
|
117 | 117 | |
|
118 | 118 | # Database |
|
119 | 119 | (_('Database'), val('database')['url'], state('database')), |
|
120 | 120 | (_('Database version'), val('database')['version'], state('database')), |
|
121 | 121 | ('', '', ''), # spacer |
|
122 | 122 | |
|
123 | 123 | # Platform/Python |
|
124 | 124 | (_('Platform'), val('platform')['name'], state('platform')), |
|
125 | 125 | (_('Platform UUID'), val('platform')['uuid'], state('platform')), |
|
126 | 126 | (_('Python version'), val('python')['version'], state('python')), |
|
127 | 127 | (_('Python path'), val('python')['executable'], state('python')), |
|
128 | 128 | ('', '', ''), # spacer |
|
129 | 129 | |
|
130 | 130 | # Systems stats |
|
131 | 131 | (_('CPU'), val('cpu')['text'], state('cpu')), |
|
132 | 132 | (_('Load'), val('load')['text'], state('load')), |
|
133 | 133 | (_('Memory'), val('memory')['text'], state('memory')), |
|
134 | 134 | (_('Uptime'), val('uptime')['text'], state('uptime')), |
|
135 | 135 | ('', '', ''), # spacer |
|
136 | 136 | |
|
137 | 137 | # Repo storage |
|
138 | 138 | (_('Storage location'), val('storage')['path'], state('storage')), |
|
139 | 139 | (_('Storage info'), val('storage')['text'], state('storage')), |
|
140 | 140 | (_('Storage inodes'), val('storage_inodes')['text'], state('storage_inodes')), |
|
141 | 141 | |
|
142 | 142 | (_('Gist storage location'), val('storage_gist')['path'], state('storage_gist')), |
|
143 | 143 | (_('Gist storage info'), val('storage_gist')['text'], state('storage_gist')), |
|
144 | 144 | |
|
145 | 145 | (_('Archive cache storage location'), val('storage_archive')['path'], state('storage_archive')), |
|
146 | 146 | (_('Archive cache info'), val('storage_archive')['text'], state('storage_archive')), |
|
147 | 147 | |
|
148 | 148 | (_('Temp storage location'), val('storage_temp')['path'], state('storage_temp')), |
|
149 | 149 | (_('Temp storage info'), val('storage_temp')['text'], state('storage_temp')), |
|
150 | 150 | |
|
151 | 151 | (_('Search info'), val('search')['text'], state('search')), |
|
152 | 152 | (_('Search location'), val('search')['location'], state('search')), |
|
153 | 153 | ('', '', ''), # spacer |
|
154 | 154 | |
|
155 | 155 | # VCS specific |
|
156 | 156 | (_('VCS Backends'), val('vcs_backends'), state('vcs_backends')), |
|
157 | 157 | (_('VCS Server'), val('vcs_server')['text'], state('vcs_server')), |
|
158 | 158 | (_('GIT'), val('git'), state('git')), |
|
159 | 159 | (_('HG'), val('hg'), state('hg')), |
|
160 | 160 | (_('SVN'), val('svn'), state('svn')), |
|
161 | 161 | |
|
162 | 162 | ] |
|
163 | 163 | |
|
164 | 164 | if snapshot: |
|
165 | 165 | if c.allowed_to_snapshot: |
|
166 | 166 | c.data_items.pop(0) # remove server info |
|
167 | 167 | self.request.override_renderer = 'admin/settings/settings_system_snapshot.mako' |
|
168 | 168 | else: |
|
169 | 169 | self.request.session.flash( |
|
170 | 170 | 'You are not allowed to do this', queue='warning') |
|
171 | 171 | return self._get_template_context(c) |
|
172 | 172 | |
|
173 | 173 | @LoginRequired() |
|
174 | 174 | @HasPermissionAllDecorator('hg.admin') |
|
175 | 175 | @view_config( |
|
176 | 176 | route_name='admin_settings_system_update', request_method='GET', |
|
177 | 177 | renderer='rhodecode:templates/admin/settings/settings_system_update.mako') |
|
178 | 178 | def settings_system_info_check_update(self): |
|
179 | 179 | _ = self.request.translate |
|
180 | 180 | c = self.load_default_context() |
|
181 | 181 | |
|
182 | 182 | update_url = self.get_update_url() |
|
183 | 183 | |
|
184 | 184 | _err = lambda s: '<div style="color:#ff8888; padding:4px 0px">{}</div>'.format(s) |
|
185 | 185 | try: |
|
186 | 186 | data = self.get_update_data(update_url) |
|
187 | 187 | except urllib2.URLError as e: |
|
188 | 188 | log.exception("Exception contacting upgrade server") |
|
189 | 189 | self.request.override_renderer = 'string' |
|
190 | 190 | return _err('Failed to contact upgrade server: %r' % e) |
|
191 | 191 | except ValueError as e: |
|
192 | 192 | log.exception("Bad data sent from update server") |
|
193 | 193 | self.request.override_renderer = 'string' |
|
194 | 194 | return _err('Bad data sent from update server') |
|
195 | 195 | |
|
196 | 196 | latest = data['versions'][0] |
|
197 | 197 | |
|
198 | 198 | c.update_url = update_url |
|
199 | 199 | c.latest_data = latest |
|
200 | 200 | c.latest_ver = latest['version'] |
|
201 | 201 | c.cur_ver = rhodecode.__version__ |
|
202 | 202 | c.should_upgrade = False |
|
203 | 203 | |
|
204 | 204 | if (packaging.version.Version(c.latest_ver) > |
|
205 | 205 | packaging.version.Version(c.cur_ver)): |
|
206 | 206 | c.should_upgrade = True |
|
207 | 207 | c.important_notices = latest['general'] |
|
208 | 208 | |
|
209 | 209 | return self._get_template_context(c) |
@@ -1,248 +1,248 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | |
|
3 | 3 | # Copyright (C) 2016-2017 RhodeCode GmbH |
|
4 | 4 | # |
|
5 | 5 | # This program is free software: you can redistribute it and/or modify |
|
6 | 6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
7 | 7 | # (only), as published by the Free Software Foundation. |
|
8 | 8 | # |
|
9 | 9 | # This program is distributed in the hope that it will be useful, |
|
10 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 | 12 | # GNU General Public License for more details. |
|
13 | 13 | # |
|
14 | 14 | # You should have received a copy of the GNU Affero General Public License |
|
15 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | 16 | # |
|
17 | 17 | # This program is dual-licensed. If you wish to learn more about the |
|
18 | 18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
19 | 19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
20 | 20 | |
|
21 | 21 | import logging |
|
22 | 22 | |
|
23 | 23 | import formencode |
|
24 | 24 | import formencode.htmlfill |
|
25 | 25 | |
|
26 | 26 | from pyramid.httpexceptions import HTTPFound |
|
27 | 27 | from pyramid.view import view_config |
|
28 | 28 | from pyramid.response import Response |
|
29 | 29 | from pyramid.renderers import render |
|
30 | 30 | |
|
31 | 31 | from rhodecode.apps._base import BaseAppView, DataGridAppView |
|
32 | 32 | from rhodecode.lib.auth import ( |
|
33 | 33 | LoginRequired, NotAnonymous, CSRFRequired, HasPermissionAnyDecorator) |
|
34 | 34 | from rhodecode.lib import helpers as h, audit_logger |
|
35 | 35 | from rhodecode.lib.utils2 import safe_unicode |
|
36 | 36 | |
|
37 | 37 | from rhodecode.model.forms import UserGroupForm |
|
38 | 38 | from rhodecode.model.permission import PermissionModel |
|
39 | 39 | from rhodecode.model.scm import UserGroupList |
|
40 | 40 | from rhodecode.model.db import ( |
|
41 | 41 | or_, count, User, UserGroup, UserGroupMember) |
|
42 | 42 | from rhodecode.model.meta import Session |
|
43 | 43 | from rhodecode.model.user_group import UserGroupModel |
|
44 | 44 | |
|
45 | 45 | log = logging.getLogger(__name__) |
|
46 | 46 | |
|
47 | 47 | |
|
48 | 48 | class AdminUserGroupsView(BaseAppView, DataGridAppView): |
|
49 | 49 | |
|
50 | 50 | def load_default_context(self): |
|
51 | 51 | c = self._get_local_tmpl_context() |
|
52 | 52 | |
|
53 | 53 | PermissionModel().set_global_permission_choices( |
|
54 | 54 | c, gettext_translator=self.request.translate) |
|
55 | 55 | |
|
56 | self._register_global_c(c) | |
|
56 | ||
|
57 | 57 | return c |
|
58 | 58 | |
|
59 | 59 | # permission check in data loading of |
|
60 | 60 | # `user_groups_list_data` via UserGroupList |
|
61 | 61 | @LoginRequired() |
|
62 | 62 | @NotAnonymous() |
|
63 | 63 | @view_config( |
|
64 | 64 | route_name='user_groups', request_method='GET', |
|
65 | 65 | renderer='rhodecode:templates/admin/user_groups/user_groups.mako') |
|
66 | 66 | def user_groups_list(self): |
|
67 | 67 | c = self.load_default_context() |
|
68 | 68 | return self._get_template_context(c) |
|
69 | 69 | |
|
70 | 70 | # permission check inside |
|
71 | 71 | @LoginRequired() |
|
72 | 72 | @NotAnonymous() |
|
73 | 73 | @view_config( |
|
74 | 74 | route_name='user_groups_data', request_method='GET', |
|
75 | 75 | renderer='json_ext', xhr=True) |
|
76 | 76 | def user_groups_list_data(self): |
|
77 | 77 | self.load_default_context() |
|
78 | 78 | column_map = { |
|
79 | 79 | 'active': 'users_group_active', |
|
80 | 80 | 'description': 'user_group_description', |
|
81 | 81 | 'members': 'members_total', |
|
82 | 82 | 'owner': 'user_username', |
|
83 | 83 | 'sync': 'group_data' |
|
84 | 84 | } |
|
85 | 85 | draw, start, limit = self._extract_chunk(self.request) |
|
86 | 86 | search_q, order_by, order_dir = self._extract_ordering( |
|
87 | 87 | self.request, column_map=column_map) |
|
88 | 88 | |
|
89 | 89 | _render = self.request.get_partial_renderer( |
|
90 | 90 | 'rhodecode:templates/data_table/_dt_elements.mako') |
|
91 | 91 | |
|
92 | 92 | def user_group_name(user_group_id, user_group_name): |
|
93 | 93 | return _render("user_group_name", user_group_id, user_group_name) |
|
94 | 94 | |
|
95 | 95 | def user_group_actions(user_group_id, user_group_name): |
|
96 | 96 | return _render("user_group_actions", user_group_id, user_group_name) |
|
97 | 97 | |
|
98 | 98 | def user_profile(username): |
|
99 | 99 | return _render('user_profile', username) |
|
100 | 100 | |
|
101 | 101 | auth_user_group_list = UserGroupList( |
|
102 | 102 | UserGroup.query().all(), perm_set=['usergroup.admin']) |
|
103 | 103 | |
|
104 | 104 | allowed_ids = [-1] |
|
105 | 105 | for user_group in auth_user_group_list: |
|
106 | 106 | allowed_ids.append(user_group.users_group_id) |
|
107 | 107 | |
|
108 | 108 | user_groups_data_total_count = UserGroup.query()\ |
|
109 | 109 | .filter(UserGroup.users_group_id.in_(allowed_ids))\ |
|
110 | 110 | .count() |
|
111 | 111 | |
|
112 | 112 | member_count = count(UserGroupMember.user_id) |
|
113 | 113 | base_q = Session.query( |
|
114 | 114 | UserGroup.users_group_name, |
|
115 | 115 | UserGroup.user_group_description, |
|
116 | 116 | UserGroup.users_group_active, |
|
117 | 117 | UserGroup.users_group_id, |
|
118 | 118 | UserGroup.group_data, |
|
119 | 119 | User, |
|
120 | 120 | member_count.label('member_count') |
|
121 | 121 | ) \ |
|
122 | 122 | .filter(UserGroup.users_group_id.in_(allowed_ids)) \ |
|
123 | 123 | .outerjoin(UserGroupMember) \ |
|
124 | 124 | .join(User, User.user_id == UserGroup.user_id) \ |
|
125 | 125 | .group_by(UserGroup, User) |
|
126 | 126 | |
|
127 | 127 | if search_q: |
|
128 | 128 | like_expression = u'%{}%'.format(safe_unicode(search_q)) |
|
129 | 129 | base_q = base_q.filter(or_( |
|
130 | 130 | UserGroup.users_group_name.ilike(like_expression), |
|
131 | 131 | )) |
|
132 | 132 | |
|
133 | 133 | user_groups_data_total_filtered_count = base_q.count() |
|
134 | 134 | |
|
135 | 135 | if order_by == 'members_total': |
|
136 | 136 | sort_col = member_count |
|
137 | 137 | elif order_by == 'user_username': |
|
138 | 138 | sort_col = User.username |
|
139 | 139 | else: |
|
140 | 140 | sort_col = getattr(UserGroup, order_by, None) |
|
141 | 141 | |
|
142 | 142 | if isinstance(sort_col, count) or sort_col: |
|
143 | 143 | if order_dir == 'asc': |
|
144 | 144 | sort_col = sort_col.asc() |
|
145 | 145 | else: |
|
146 | 146 | sort_col = sort_col.desc() |
|
147 | 147 | |
|
148 | 148 | base_q = base_q.order_by(sort_col) |
|
149 | 149 | base_q = base_q.offset(start).limit(limit) |
|
150 | 150 | |
|
151 | 151 | # authenticated access to user groups |
|
152 | 152 | auth_user_group_list = base_q.all() |
|
153 | 153 | |
|
154 | 154 | user_groups_data = [] |
|
155 | 155 | for user_gr in auth_user_group_list: |
|
156 | 156 | user_groups_data.append({ |
|
157 | 157 | "users_group_name": user_group_name( |
|
158 | 158 | user_gr.users_group_id, h.escape(user_gr.users_group_name)), |
|
159 | 159 | "name_raw": h.escape(user_gr.users_group_name), |
|
160 | 160 | "description": h.escape(user_gr.user_group_description), |
|
161 | 161 | "members": user_gr.member_count, |
|
162 | 162 | # NOTE(marcink): because of advanced query we |
|
163 | 163 | # need to load it like that |
|
164 | 164 | "sync": UserGroup._load_group_data( |
|
165 | 165 | user_gr.group_data).get('extern_type'), |
|
166 | 166 | "active": h.bool2icon(user_gr.users_group_active), |
|
167 | 167 | "owner": user_profile(user_gr.User.username), |
|
168 | 168 | "action": user_group_actions( |
|
169 | 169 | user_gr.users_group_id, user_gr.users_group_name) |
|
170 | 170 | }) |
|
171 | 171 | |
|
172 | 172 | data = ({ |
|
173 | 173 | 'draw': draw, |
|
174 | 174 | 'data': user_groups_data, |
|
175 | 175 | 'recordsTotal': user_groups_data_total_count, |
|
176 | 176 | 'recordsFiltered': user_groups_data_total_filtered_count, |
|
177 | 177 | }) |
|
178 | 178 | |
|
179 | 179 | return data |
|
180 | 180 | |
|
181 | 181 | @LoginRequired() |
|
182 | 182 | @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true') |
|
183 | 183 | @view_config( |
|
184 | 184 | route_name='user_groups_new', request_method='GET', |
|
185 | 185 | renderer='rhodecode:templates/admin/user_groups/user_group_add.mako') |
|
186 | 186 | def user_groups_new(self): |
|
187 | 187 | c = self.load_default_context() |
|
188 | 188 | return self._get_template_context(c) |
|
189 | 189 | |
|
190 | 190 | @LoginRequired() |
|
191 | 191 | @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true') |
|
192 | 192 | @CSRFRequired() |
|
193 | 193 | @view_config( |
|
194 | 194 | route_name='user_groups_create', request_method='POST', |
|
195 | 195 | renderer='rhodecode:templates/admin/user_groups/user_group_add.mako') |
|
196 | 196 | def user_groups_create(self): |
|
197 | 197 | _ = self.request.translate |
|
198 | 198 | c = self.load_default_context() |
|
199 | users_group_form = UserGroupForm()() | |
|
199 | users_group_form = UserGroupForm(self.request.translate)() | |
|
200 | 200 | |
|
201 | 201 | user_group_name = self.request.POST.get('users_group_name') |
|
202 | 202 | try: |
|
203 | 203 | form_result = users_group_form.to_python(dict(self.request.POST)) |
|
204 | 204 | user_group = UserGroupModel().create( |
|
205 | 205 | name=form_result['users_group_name'], |
|
206 | 206 | description=form_result['user_group_description'], |
|
207 | 207 | owner=self._rhodecode_user.user_id, |
|
208 | 208 | active=form_result['users_group_active']) |
|
209 | 209 | Session().flush() |
|
210 | 210 | creation_data = user_group.get_api_data() |
|
211 | 211 | user_group_name = form_result['users_group_name'] |
|
212 | 212 | |
|
213 | 213 | audit_logger.store_web( |
|
214 | 214 | 'user_group.create', action_data={'data': creation_data}, |
|
215 | 215 | user=self._rhodecode_user) |
|
216 | 216 | |
|
217 | 217 | user_group_link = h.link_to( |
|
218 | 218 | h.escape(user_group_name), |
|
219 | 219 | h.route_path( |
|
220 | 220 | 'edit_user_group', user_group_id=user_group.users_group_id)) |
|
221 | 221 | h.flash(h.literal(_('Created user group %(user_group_link)s') |
|
222 | 222 | % {'user_group_link': user_group_link}), |
|
223 | 223 | category='success') |
|
224 | 224 | Session().commit() |
|
225 | 225 | user_group_id = user_group.users_group_id |
|
226 | 226 | except formencode.Invalid as errors: |
|
227 | 227 | |
|
228 | 228 | data = render( |
|
229 | 229 | 'rhodecode:templates/admin/user_groups/user_group_add.mako', |
|
230 | 230 | self._get_template_context(c), self.request) |
|
231 | 231 | html = formencode.htmlfill.render( |
|
232 | 232 | data, |
|
233 | 233 | defaults=errors.value, |
|
234 | 234 | errors=errors.error_dict or {}, |
|
235 | 235 | prefix_error=False, |
|
236 | 236 | encoding="UTF-8", |
|
237 | 237 | force_defaults=False |
|
238 | 238 | ) |
|
239 | 239 | return Response(html) |
|
240 | 240 | |
|
241 | 241 | except Exception: |
|
242 | 242 | log.exception("Exception creating user group") |
|
243 | 243 | h.flash(_('Error occurred during creation of user group %s') \ |
|
244 | 244 | % user_group_name, category='error') |
|
245 | 245 | raise HTTPFound(h.route_path('user_groups_new')) |
|
246 | 246 | |
|
247 | 247 | raise HTTPFound( |
|
248 | 248 | h.route_path('edit_user_group', user_group_id=user_group_id)) |
@@ -1,1179 +1,1190 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | |
|
3 | 3 | # Copyright (C) 2016-2017 RhodeCode GmbH |
|
4 | 4 | # |
|
5 | 5 | # This program is free software: you can redistribute it and/or modify |
|
6 | 6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
7 | 7 | # (only), as published by the Free Software Foundation. |
|
8 | 8 | # |
|
9 | 9 | # This program is distributed in the hope that it will be useful, |
|
10 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 | 12 | # GNU General Public License for more details. |
|
13 | 13 | # |
|
14 | 14 | # You should have received a copy of the GNU Affero General Public License |
|
15 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | 16 | # |
|
17 | 17 | # This program is dual-licensed. If you wish to learn more about the |
|
18 | 18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
19 | 19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
20 | 20 | |
|
21 | 21 | import logging |
|
22 | 22 | import datetime |
|
23 | 23 | import formencode |
|
24 | 24 | import formencode.htmlfill |
|
25 | 25 | |
|
26 | 26 | from pyramid.httpexceptions import HTTPFound |
|
27 | 27 | from pyramid.view import view_config |
|
28 | 28 | from pyramid.renderers import render |
|
29 | 29 | from pyramid.response import Response |
|
30 | 30 | |
|
31 | 31 | from rhodecode.apps._base import BaseAppView, DataGridAppView, UserAppView |
|
32 | 32 | from rhodecode.apps.ssh_support import SshKeyFileChangeEvent |
|
33 | 33 | from rhodecode.authentication.plugins import auth_rhodecode |
|
34 | 34 | from rhodecode.events import trigger |
|
35 | 35 | |
|
36 | 36 | from rhodecode.lib import audit_logger |
|
37 | 37 | from rhodecode.lib.exceptions import ( |
|
38 | 38 | UserCreationError, UserOwnsReposException, UserOwnsRepoGroupsException, |
|
39 | 39 | UserOwnsUserGroupsException, DefaultUserException) |
|
40 | 40 | from rhodecode.lib.ext_json import json |
|
41 | 41 | from rhodecode.lib.auth import ( |
|
42 | 42 | LoginRequired, HasPermissionAllDecorator, CSRFRequired) |
|
43 | 43 | from rhodecode.lib import helpers as h |
|
44 | 44 | from rhodecode.lib.utils2 import safe_int, safe_unicode, AttributeDict |
|
45 | 45 | from rhodecode.model.auth_token import AuthTokenModel |
|
46 | 46 | from rhodecode.model.forms import ( |
|
47 |
UserForm, UserIndividualPermissionsForm, UserPermissionsForm |
|
|
47 | UserForm, UserIndividualPermissionsForm, UserPermissionsForm, | |
|
48 | UserExtraEmailForm, UserExtraIpForm) | |
|
48 | 49 | from rhodecode.model.permission import PermissionModel |
|
49 | 50 | from rhodecode.model.repo_group import RepoGroupModel |
|
50 | 51 | from rhodecode.model.ssh_key import SshKeyModel |
|
51 | 52 | from rhodecode.model.user import UserModel |
|
52 | 53 | from rhodecode.model.user_group import UserGroupModel |
|
53 | 54 | from rhodecode.model.db import ( |
|
54 | 55 | or_, coalesce,IntegrityError, User, UserGroup, UserIpMap, UserEmailMap, |
|
55 | 56 | UserApiKeys, UserSshKeys, RepoGroup) |
|
56 | 57 | from rhodecode.model.meta import Session |
|
57 | 58 | |
|
58 | 59 | log = logging.getLogger(__name__) |
|
59 | 60 | |
|
60 | 61 | |
|
61 | 62 | class AdminUsersView(BaseAppView, DataGridAppView): |
|
62 | 63 | |
|
63 | 64 | def load_default_context(self): |
|
64 | 65 | c = self._get_local_tmpl_context() |
|
65 | self._register_global_c(c) | |
|
66 | 66 | return c |
|
67 | 67 | |
|
68 | 68 | @LoginRequired() |
|
69 | 69 | @HasPermissionAllDecorator('hg.admin') |
|
70 | 70 | @view_config( |
|
71 | 71 | route_name='users', request_method='GET', |
|
72 | 72 | renderer='rhodecode:templates/admin/users/users.mako') |
|
73 | 73 | def users_list(self): |
|
74 | 74 | c = self.load_default_context() |
|
75 | 75 | return self._get_template_context(c) |
|
76 | 76 | |
|
77 | 77 | @LoginRequired() |
|
78 | 78 | @HasPermissionAllDecorator('hg.admin') |
|
79 | 79 | @view_config( |
|
80 | 80 | # renderer defined below |
|
81 | 81 | route_name='users_data', request_method='GET', |
|
82 | 82 | renderer='json_ext', xhr=True) |
|
83 | 83 | def users_list_data(self): |
|
84 | 84 | self.load_default_context() |
|
85 | 85 | column_map = { |
|
86 | 86 | 'first_name': 'name', |
|
87 | 87 | 'last_name': 'lastname', |
|
88 | 88 | } |
|
89 | 89 | draw, start, limit = self._extract_chunk(self.request) |
|
90 | 90 | search_q, order_by, order_dir = self._extract_ordering( |
|
91 | 91 | self.request, column_map=column_map) |
|
92 | 92 | |
|
93 | 93 | _render = self.request.get_partial_renderer( |
|
94 | 94 | 'rhodecode:templates/data_table/_dt_elements.mako') |
|
95 | 95 | |
|
96 | 96 | def user_actions(user_id, username): |
|
97 | 97 | return _render("user_actions", user_id, username) |
|
98 | 98 | |
|
99 | 99 | users_data_total_count = User.query()\ |
|
100 | 100 | .filter(User.username != User.DEFAULT_USER) \ |
|
101 | 101 | .count() |
|
102 | 102 | |
|
103 | 103 | # json generate |
|
104 | 104 | base_q = User.query().filter(User.username != User.DEFAULT_USER) |
|
105 | 105 | |
|
106 | 106 | if search_q: |
|
107 | 107 | like_expression = u'%{}%'.format(safe_unicode(search_q)) |
|
108 | 108 | base_q = base_q.filter(or_( |
|
109 | 109 | User.username.ilike(like_expression), |
|
110 | 110 | User._email.ilike(like_expression), |
|
111 | 111 | User.name.ilike(like_expression), |
|
112 | 112 | User.lastname.ilike(like_expression), |
|
113 | 113 | )) |
|
114 | 114 | |
|
115 | 115 | users_data_total_filtered_count = base_q.count() |
|
116 | 116 | |
|
117 | 117 | sort_col = getattr(User, order_by, None) |
|
118 | 118 | if sort_col: |
|
119 | 119 | if order_dir == 'asc': |
|
120 | 120 | # handle null values properly to order by NULL last |
|
121 | 121 | if order_by in ['last_activity']: |
|
122 | 122 | sort_col = coalesce(sort_col, datetime.date.max) |
|
123 | 123 | sort_col = sort_col.asc() |
|
124 | 124 | else: |
|
125 | 125 | # handle null values properly to order by NULL last |
|
126 | 126 | if order_by in ['last_activity']: |
|
127 | 127 | sort_col = coalesce(sort_col, datetime.date.min) |
|
128 | 128 | sort_col = sort_col.desc() |
|
129 | 129 | |
|
130 | 130 | base_q = base_q.order_by(sort_col) |
|
131 | 131 | base_q = base_q.offset(start).limit(limit) |
|
132 | 132 | |
|
133 | 133 | users_list = base_q.all() |
|
134 | 134 | |
|
135 | 135 | users_data = [] |
|
136 | 136 | for user in users_list: |
|
137 | 137 | users_data.append({ |
|
138 | 138 | "username": h.gravatar_with_user(self.request, user.username), |
|
139 | 139 | "email": user.email, |
|
140 | 140 | "first_name": user.first_name, |
|
141 | 141 | "last_name": user.last_name, |
|
142 | 142 | "last_login": h.format_date(user.last_login), |
|
143 | 143 | "last_activity": h.format_date(user.last_activity), |
|
144 | 144 | "active": h.bool2icon(user.active), |
|
145 | 145 | "active_raw": user.active, |
|
146 | 146 | "admin": h.bool2icon(user.admin), |
|
147 | 147 | "extern_type": user.extern_type, |
|
148 | 148 | "extern_name": user.extern_name, |
|
149 | 149 | "action": user_actions(user.user_id, user.username), |
|
150 | 150 | }) |
|
151 | 151 | |
|
152 | 152 | data = ({ |
|
153 | 153 | 'draw': draw, |
|
154 | 154 | 'data': users_data, |
|
155 | 155 | 'recordsTotal': users_data_total_count, |
|
156 | 156 | 'recordsFiltered': users_data_total_filtered_count, |
|
157 | 157 | }) |
|
158 | 158 | |
|
159 | 159 | return data |
|
160 | 160 | |
|
161 | 161 | def _set_personal_repo_group_template_vars(self, c_obj): |
|
162 | 162 | DummyUser = AttributeDict({ |
|
163 | 163 | 'username': '${username}', |
|
164 | 164 | 'user_id': '${user_id}', |
|
165 | 165 | }) |
|
166 | 166 | c_obj.default_create_repo_group = RepoGroupModel() \ |
|
167 | 167 | .get_default_create_personal_repo_group() |
|
168 | 168 | c_obj.personal_repo_group_name = RepoGroupModel() \ |
|
169 | 169 | .get_personal_group_name(DummyUser) |
|
170 | 170 | |
|
171 | 171 | @LoginRequired() |
|
172 | 172 | @HasPermissionAllDecorator('hg.admin') |
|
173 | 173 | @view_config( |
|
174 | 174 | route_name='users_new', request_method='GET', |
|
175 | 175 | renderer='rhodecode:templates/admin/users/user_add.mako') |
|
176 | 176 | def users_new(self): |
|
177 | 177 | _ = self.request.translate |
|
178 | 178 | c = self.load_default_context() |
|
179 | 179 | c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name |
|
180 | 180 | self._set_personal_repo_group_template_vars(c) |
|
181 | 181 | return self._get_template_context(c) |
|
182 | 182 | |
|
183 | 183 | @LoginRequired() |
|
184 | 184 | @HasPermissionAllDecorator('hg.admin') |
|
185 | 185 | @CSRFRequired() |
|
186 | 186 | @view_config( |
|
187 | 187 | route_name='users_create', request_method='POST', |
|
188 | 188 | renderer='rhodecode:templates/admin/users/user_add.mako') |
|
189 | 189 | def users_create(self): |
|
190 | 190 | _ = self.request.translate |
|
191 | 191 | c = self.load_default_context() |
|
192 | 192 | c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name |
|
193 | 193 | user_model = UserModel() |
|
194 | user_form = UserForm()() | |
|
194 | user_form = UserForm(self.request.translate)() | |
|
195 | 195 | try: |
|
196 | 196 | form_result = user_form.to_python(dict(self.request.POST)) |
|
197 | 197 | user = user_model.create(form_result) |
|
198 | 198 | Session().flush() |
|
199 | 199 | creation_data = user.get_api_data() |
|
200 | 200 | username = form_result['username'] |
|
201 | 201 | |
|
202 | 202 | audit_logger.store_web( |
|
203 | 203 | 'user.create', action_data={'data': creation_data}, |
|
204 | 204 | user=c.rhodecode_user) |
|
205 | 205 | |
|
206 | 206 | user_link = h.link_to( |
|
207 | 207 | h.escape(username), |
|
208 | 208 | h.route_path('user_edit', user_id=user.user_id)) |
|
209 | 209 | h.flash(h.literal(_('Created user %(user_link)s') |
|
210 | 210 | % {'user_link': user_link}), category='success') |
|
211 | 211 | Session().commit() |
|
212 | 212 | except formencode.Invalid as errors: |
|
213 | 213 | self._set_personal_repo_group_template_vars(c) |
|
214 | 214 | data = render( |
|
215 | 215 | 'rhodecode:templates/admin/users/user_add.mako', |
|
216 | 216 | self._get_template_context(c), self.request) |
|
217 | 217 | html = formencode.htmlfill.render( |
|
218 | 218 | data, |
|
219 | 219 | defaults=errors.value, |
|
220 | 220 | errors=errors.error_dict or {}, |
|
221 | 221 | prefix_error=False, |
|
222 | 222 | encoding="UTF-8", |
|
223 | 223 | force_defaults=False |
|
224 | 224 | ) |
|
225 | 225 | return Response(html) |
|
226 | 226 | except UserCreationError as e: |
|
227 | 227 | h.flash(e, 'error') |
|
228 | 228 | except Exception: |
|
229 | 229 | log.exception("Exception creation of user") |
|
230 | 230 | h.flash(_('Error occurred during creation of user %s') |
|
231 | 231 | % self.request.POST.get('username'), category='error') |
|
232 | 232 | raise HTTPFound(h.route_path('users')) |
|
233 | 233 | |
|
234 | 234 | |
|
235 | 235 | class UsersView(UserAppView): |
|
236 | 236 | ALLOW_SCOPED_TOKENS = False |
|
237 | 237 | """ |
|
238 | 238 | This view has alternative version inside EE, if modified please take a look |
|
239 | 239 | in there as well. |
|
240 | 240 | """ |
|
241 | 241 | |
|
242 | 242 | def load_default_context(self): |
|
243 | 243 | c = self._get_local_tmpl_context() |
|
244 | 244 | c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS |
|
245 | 245 | c.allowed_languages = [ |
|
246 | 246 | ('en', 'English (en)'), |
|
247 | 247 | ('de', 'German (de)'), |
|
248 | 248 | ('fr', 'French (fr)'), |
|
249 | 249 | ('it', 'Italian (it)'), |
|
250 | 250 | ('ja', 'Japanese (ja)'), |
|
251 | 251 | ('pl', 'Polish (pl)'), |
|
252 | 252 | ('pt', 'Portuguese (pt)'), |
|
253 | 253 | ('ru', 'Russian (ru)'), |
|
254 | 254 | ('zh', 'Chinese (zh)'), |
|
255 | 255 | ] |
|
256 | 256 | req = self.request |
|
257 | 257 | |
|
258 | 258 | c.available_permissions = req.registry.settings['available_permissions'] |
|
259 | 259 | PermissionModel().set_global_permission_choices( |
|
260 | 260 | c, gettext_translator=req.translate) |
|
261 | 261 | |
|
262 | self._register_global_c(c) | |
|
262 | ||
|
263 | 263 | return c |
|
264 | 264 | |
|
265 | 265 | @LoginRequired() |
|
266 | 266 | @HasPermissionAllDecorator('hg.admin') |
|
267 | 267 | @CSRFRequired() |
|
268 | 268 | @view_config( |
|
269 | 269 | route_name='user_update', request_method='POST', |
|
270 | 270 | renderer='rhodecode:templates/admin/users/user_edit.mako') |
|
271 | 271 | def user_update(self): |
|
272 | 272 | _ = self.request.translate |
|
273 | 273 | c = self.load_default_context() |
|
274 | 274 | |
|
275 | 275 | user_id = self.db_user_id |
|
276 | 276 | c.user = self.db_user |
|
277 | 277 | |
|
278 | 278 | c.active = 'profile' |
|
279 | 279 | c.extern_type = c.user.extern_type |
|
280 | 280 | c.extern_name = c.user.extern_name |
|
281 | 281 | c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr) |
|
282 | 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 | 285 | old_data={'user_id': user_id, |
|
285 | 286 | 'email': c.user.email})() |
|
286 | 287 | form_result = {} |
|
287 | 288 | old_values = c.user.get_api_data() |
|
288 | 289 | try: |
|
289 | 290 | form_result = _form.to_python(dict(self.request.POST)) |
|
290 | 291 | skip_attrs = ['extern_type', 'extern_name'] |
|
291 | 292 | # TODO: plugin should define if username can be updated |
|
292 | 293 | if c.extern_type != "rhodecode": |
|
293 | 294 | # forbid updating username for external accounts |
|
294 | 295 | skip_attrs.append('username') |
|
295 | 296 | |
|
296 | 297 | UserModel().update_user( |
|
297 | 298 | user_id, skip_attrs=skip_attrs, **form_result) |
|
298 | 299 | |
|
299 | 300 | audit_logger.store_web( |
|
300 | 301 | 'user.edit', action_data={'old_data': old_values}, |
|
301 | 302 | user=c.rhodecode_user) |
|
302 | 303 | |
|
303 | 304 | Session().commit() |
|
304 | 305 | h.flash(_('User updated successfully'), category='success') |
|
305 | 306 | except formencode.Invalid as errors: |
|
306 | 307 | data = render( |
|
307 | 308 | 'rhodecode:templates/admin/users/user_edit.mako', |
|
308 | 309 | self._get_template_context(c), self.request) |
|
309 | 310 | html = formencode.htmlfill.render( |
|
310 | 311 | data, |
|
311 | 312 | defaults=errors.value, |
|
312 | 313 | errors=errors.error_dict or {}, |
|
313 | 314 | prefix_error=False, |
|
314 | 315 | encoding="UTF-8", |
|
315 | 316 | force_defaults=False |
|
316 | 317 | ) |
|
317 | 318 | return Response(html) |
|
318 | 319 | except UserCreationError as e: |
|
319 | 320 | h.flash(e, 'error') |
|
320 | 321 | except Exception: |
|
321 | 322 | log.exception("Exception updating user") |
|
322 | 323 | h.flash(_('Error occurred during update of user %s') |
|
323 | 324 | % form_result.get('username'), category='error') |
|
324 | 325 | raise HTTPFound(h.route_path('user_edit', user_id=user_id)) |
|
325 | 326 | |
|
326 | 327 | @LoginRequired() |
|
327 | 328 | @HasPermissionAllDecorator('hg.admin') |
|
328 | 329 | @CSRFRequired() |
|
329 | 330 | @view_config( |
|
330 | 331 | route_name='user_delete', request_method='POST', |
|
331 | 332 | renderer='rhodecode:templates/admin/users/user_edit.mako') |
|
332 | 333 | def user_delete(self): |
|
333 | 334 | _ = self.request.translate |
|
334 | 335 | c = self.load_default_context() |
|
335 | 336 | c.user = self.db_user |
|
336 | 337 | |
|
337 | 338 | _repos = c.user.repositories |
|
338 | 339 | _repo_groups = c.user.repository_groups |
|
339 | 340 | _user_groups = c.user.user_groups |
|
340 | 341 | |
|
341 | 342 | handle_repos = None |
|
342 | 343 | handle_repo_groups = None |
|
343 | 344 | handle_user_groups = None |
|
344 | 345 | # dummy call for flash of handle |
|
345 | 346 | set_handle_flash_repos = lambda: None |
|
346 | 347 | set_handle_flash_repo_groups = lambda: None |
|
347 | 348 | set_handle_flash_user_groups = lambda: None |
|
348 | 349 | |
|
349 | 350 | if _repos and self.request.POST.get('user_repos'): |
|
350 | 351 | do = self.request.POST['user_repos'] |
|
351 | 352 | if do == 'detach': |
|
352 | 353 | handle_repos = 'detach' |
|
353 | 354 | set_handle_flash_repos = lambda: h.flash( |
|
354 | 355 | _('Detached %s repositories') % len(_repos), |
|
355 | 356 | category='success') |
|
356 | 357 | elif do == 'delete': |
|
357 | 358 | handle_repos = 'delete' |
|
358 | 359 | set_handle_flash_repos = lambda: h.flash( |
|
359 | 360 | _('Deleted %s repositories') % len(_repos), |
|
360 | 361 | category='success') |
|
361 | 362 | |
|
362 | 363 | if _repo_groups and self.request.POST.get('user_repo_groups'): |
|
363 | 364 | do = self.request.POST['user_repo_groups'] |
|
364 | 365 | if do == 'detach': |
|
365 | 366 | handle_repo_groups = 'detach' |
|
366 | 367 | set_handle_flash_repo_groups = lambda: h.flash( |
|
367 | 368 | _('Detached %s repository groups') % len(_repo_groups), |
|
368 | 369 | category='success') |
|
369 | 370 | elif do == 'delete': |
|
370 | 371 | handle_repo_groups = 'delete' |
|
371 | 372 | set_handle_flash_repo_groups = lambda: h.flash( |
|
372 | 373 | _('Deleted %s repository groups') % len(_repo_groups), |
|
373 | 374 | category='success') |
|
374 | 375 | |
|
375 | 376 | if _user_groups and self.request.POST.get('user_user_groups'): |
|
376 | 377 | do = self.request.POST['user_user_groups'] |
|
377 | 378 | if do == 'detach': |
|
378 | 379 | handle_user_groups = 'detach' |
|
379 | 380 | set_handle_flash_user_groups = lambda: h.flash( |
|
380 | 381 | _('Detached %s user groups') % len(_user_groups), |
|
381 | 382 | category='success') |
|
382 | 383 | elif do == 'delete': |
|
383 | 384 | handle_user_groups = 'delete' |
|
384 | 385 | set_handle_flash_user_groups = lambda: h.flash( |
|
385 | 386 | _('Deleted %s user groups') % len(_user_groups), |
|
386 | 387 | category='success') |
|
387 | 388 | |
|
388 | 389 | old_values = c.user.get_api_data() |
|
389 | 390 | try: |
|
390 | 391 | UserModel().delete(c.user, handle_repos=handle_repos, |
|
391 | 392 | handle_repo_groups=handle_repo_groups, |
|
392 | 393 | handle_user_groups=handle_user_groups) |
|
393 | 394 | |
|
394 | 395 | audit_logger.store_web( |
|
395 | 396 | 'user.delete', action_data={'old_data': old_values}, |
|
396 | 397 | user=c.rhodecode_user) |
|
397 | 398 | |
|
398 | 399 | Session().commit() |
|
399 | 400 | set_handle_flash_repos() |
|
400 | 401 | set_handle_flash_repo_groups() |
|
401 | 402 | set_handle_flash_user_groups() |
|
402 | 403 | h.flash(_('Successfully deleted user'), category='success') |
|
403 | 404 | except (UserOwnsReposException, UserOwnsRepoGroupsException, |
|
404 | 405 | UserOwnsUserGroupsException, DefaultUserException) as e: |
|
405 | 406 | h.flash(e, category='warning') |
|
406 | 407 | except Exception: |
|
407 | 408 | log.exception("Exception during deletion of user") |
|
408 | 409 | h.flash(_('An error occurred during deletion of user'), |
|
409 | 410 | category='error') |
|
410 | 411 | raise HTTPFound(h.route_path('users')) |
|
411 | 412 | |
|
412 | 413 | @LoginRequired() |
|
413 | 414 | @HasPermissionAllDecorator('hg.admin') |
|
414 | 415 | @view_config( |
|
415 | 416 | route_name='user_edit', request_method='GET', |
|
416 | 417 | renderer='rhodecode:templates/admin/users/user_edit.mako') |
|
417 | 418 | def user_edit(self): |
|
418 | 419 | _ = self.request.translate |
|
419 | 420 | c = self.load_default_context() |
|
420 | 421 | c.user = self.db_user |
|
421 | 422 | |
|
422 | 423 | c.active = 'profile' |
|
423 | 424 | c.extern_type = c.user.extern_type |
|
424 | 425 | c.extern_name = c.user.extern_name |
|
425 | 426 | c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr) |
|
426 | 427 | |
|
427 | 428 | defaults = c.user.get_dict() |
|
428 | 429 | defaults.update({'language': c.user.user_data.get('language')}) |
|
429 | 430 | |
|
430 | 431 | data = render( |
|
431 | 432 | 'rhodecode:templates/admin/users/user_edit.mako', |
|
432 | 433 | self._get_template_context(c), self.request) |
|
433 | 434 | html = formencode.htmlfill.render( |
|
434 | 435 | data, |
|
435 | 436 | defaults=defaults, |
|
436 | 437 | encoding="UTF-8", |
|
437 | 438 | force_defaults=False |
|
438 | 439 | ) |
|
439 | 440 | return Response(html) |
|
440 | 441 | |
|
441 | 442 | @LoginRequired() |
|
442 | 443 | @HasPermissionAllDecorator('hg.admin') |
|
443 | 444 | @view_config( |
|
444 | 445 | route_name='user_edit_advanced', request_method='GET', |
|
445 | 446 | renderer='rhodecode:templates/admin/users/user_edit.mako') |
|
446 | 447 | def user_edit_advanced(self): |
|
447 | 448 | _ = self.request.translate |
|
448 | 449 | c = self.load_default_context() |
|
449 | 450 | |
|
450 | 451 | user_id = self.db_user_id |
|
451 | 452 | c.user = self.db_user |
|
452 | 453 | |
|
453 | 454 | c.active = 'advanced' |
|
454 | 455 | c.personal_repo_group = RepoGroup.get_user_personal_repo_group(user_id) |
|
455 | 456 | c.personal_repo_group_name = RepoGroupModel()\ |
|
456 | 457 | .get_personal_group_name(c.user) |
|
457 | 458 | |
|
458 | 459 | c.user_to_review_rules = sorted( |
|
459 | 460 | (x.user for x in c.user.user_review_rules), |
|
460 | 461 | key=lambda u: u.username.lower()) |
|
461 | 462 | |
|
462 | 463 | c.first_admin = User.get_first_super_admin() |
|
463 | 464 | defaults = c.user.get_dict() |
|
464 | 465 | |
|
465 | 466 | # Interim workaround if the user participated on any pull requests as a |
|
466 | 467 | # reviewer. |
|
467 | 468 | has_review = len(c.user.reviewer_pull_requests) |
|
468 | 469 | c.can_delete_user = not has_review |
|
469 | 470 | c.can_delete_user_message = '' |
|
470 | 471 | inactive_link = h.link_to( |
|
471 | 472 | 'inactive', h.route_path('user_edit', user_id=user_id, _anchor='active')) |
|
472 | 473 | if has_review == 1: |
|
473 | 474 | c.can_delete_user_message = h.literal(_( |
|
474 | 475 | 'The user participates as reviewer in {} pull request and ' |
|
475 | 476 | 'cannot be deleted. \nYou can set the user to ' |
|
476 | 477 | '"{}" instead of deleting it.').format( |
|
477 | 478 | has_review, inactive_link)) |
|
478 | 479 | elif has_review: |
|
479 | 480 | c.can_delete_user_message = h.literal(_( |
|
480 | 481 | 'The user participates as reviewer in {} pull requests and ' |
|
481 | 482 | 'cannot be deleted. \nYou can set the user to ' |
|
482 | 483 | '"{}" instead of deleting it.').format( |
|
483 | 484 | has_review, inactive_link)) |
|
484 | 485 | |
|
485 | 486 | data = render( |
|
486 | 487 | 'rhodecode:templates/admin/users/user_edit.mako', |
|
487 | 488 | self._get_template_context(c), self.request) |
|
488 | 489 | html = formencode.htmlfill.render( |
|
489 | 490 | data, |
|
490 | 491 | defaults=defaults, |
|
491 | 492 | encoding="UTF-8", |
|
492 | 493 | force_defaults=False |
|
493 | 494 | ) |
|
494 | 495 | return Response(html) |
|
495 | 496 | |
|
496 | 497 | @LoginRequired() |
|
497 | 498 | @HasPermissionAllDecorator('hg.admin') |
|
498 | 499 | @view_config( |
|
499 | 500 | route_name='user_edit_global_perms', request_method='GET', |
|
500 | 501 | renderer='rhodecode:templates/admin/users/user_edit.mako') |
|
501 | 502 | def user_edit_global_perms(self): |
|
502 | 503 | _ = self.request.translate |
|
503 | 504 | c = self.load_default_context() |
|
504 | 505 | c.user = self.db_user |
|
505 | 506 | |
|
506 | 507 | c.active = 'global_perms' |
|
507 | 508 | |
|
508 | 509 | c.default_user = User.get_default_user() |
|
509 | 510 | defaults = c.user.get_dict() |
|
510 | 511 | defaults.update(c.default_user.get_default_perms(suffix='_inherited')) |
|
511 | 512 | defaults.update(c.default_user.get_default_perms()) |
|
512 | 513 | defaults.update(c.user.get_default_perms()) |
|
513 | 514 | |
|
514 | 515 | data = render( |
|
515 | 516 | 'rhodecode:templates/admin/users/user_edit.mako', |
|
516 | 517 | self._get_template_context(c), self.request) |
|
517 | 518 | html = formencode.htmlfill.render( |
|
518 | 519 | data, |
|
519 | 520 | defaults=defaults, |
|
520 | 521 | encoding="UTF-8", |
|
521 | 522 | force_defaults=False |
|
522 | 523 | ) |
|
523 | 524 | return Response(html) |
|
524 | 525 | |
|
525 | 526 | @LoginRequired() |
|
526 | 527 | @HasPermissionAllDecorator('hg.admin') |
|
527 | 528 | @CSRFRequired() |
|
528 | 529 | @view_config( |
|
529 | 530 | route_name='user_edit_global_perms_update', request_method='POST', |
|
530 | 531 | renderer='rhodecode:templates/admin/users/user_edit.mako') |
|
531 | 532 | def user_edit_global_perms_update(self): |
|
532 | 533 | _ = self.request.translate |
|
533 | 534 | c = self.load_default_context() |
|
534 | 535 | |
|
535 | 536 | user_id = self.db_user_id |
|
536 | 537 | c.user = self.db_user |
|
537 | 538 | |
|
538 | 539 | c.active = 'global_perms' |
|
539 | 540 | try: |
|
540 | 541 | # first stage that verifies the checkbox |
|
541 | _form = UserIndividualPermissionsForm() | |
|
542 | _form = UserIndividualPermissionsForm(self.request.translate) | |
|
542 | 543 | form_result = _form.to_python(dict(self.request.POST)) |
|
543 | 544 | inherit_perms = form_result['inherit_default_permissions'] |
|
544 | 545 | c.user.inherit_default_permissions = inherit_perms |
|
545 | 546 | Session().add(c.user) |
|
546 | 547 | |
|
547 | 548 | if not inherit_perms: |
|
548 | 549 | # only update the individual ones if we un check the flag |
|
549 | 550 | _form = UserPermissionsForm( |
|
551 | self.request.translate, | |
|
550 | 552 | [x[0] for x in c.repo_create_choices], |
|
551 | 553 | [x[0] for x in c.repo_create_on_write_choices], |
|
552 | 554 | [x[0] for x in c.repo_group_create_choices], |
|
553 | 555 | [x[0] for x in c.user_group_create_choices], |
|
554 | 556 | [x[0] for x in c.fork_choices], |
|
555 | 557 | [x[0] for x in c.inherit_default_permission_choices])() |
|
556 | 558 | |
|
557 | 559 | form_result = _form.to_python(dict(self.request.POST)) |
|
558 | 560 | form_result.update({'perm_user_id': c.user.user_id}) |
|
559 | 561 | |
|
560 | 562 | PermissionModel().update_user_permissions(form_result) |
|
561 | 563 | |
|
562 | 564 | # TODO(marcink): implement global permissions |
|
563 | 565 | # audit_log.store_web('user.edit.permissions') |
|
564 | 566 | |
|
565 | 567 | Session().commit() |
|
566 | 568 | h.flash(_('User global permissions updated successfully'), |
|
567 | 569 | category='success') |
|
568 | 570 | |
|
569 | 571 | except formencode.Invalid as errors: |
|
570 | 572 | data = render( |
|
571 | 573 | 'rhodecode:templates/admin/users/user_edit.mako', |
|
572 | 574 | self._get_template_context(c), self.request) |
|
573 | 575 | html = formencode.htmlfill.render( |
|
574 | 576 | data, |
|
575 | 577 | defaults=errors.value, |
|
576 | 578 | errors=errors.error_dict or {}, |
|
577 | 579 | prefix_error=False, |
|
578 | 580 | encoding="UTF-8", |
|
579 | 581 | force_defaults=False |
|
580 | 582 | ) |
|
581 | 583 | return Response(html) |
|
582 | 584 | except Exception: |
|
583 | 585 | log.exception("Exception during permissions saving") |
|
584 | 586 | h.flash(_('An error occurred during permissions saving'), |
|
585 | 587 | category='error') |
|
586 | 588 | raise HTTPFound(h.route_path('user_edit_global_perms', user_id=user_id)) |
|
587 | 589 | |
|
588 | 590 | @LoginRequired() |
|
589 | 591 | @HasPermissionAllDecorator('hg.admin') |
|
590 | 592 | @CSRFRequired() |
|
591 | 593 | @view_config( |
|
592 | 594 | route_name='user_force_password_reset', request_method='POST', |
|
593 | 595 | renderer='rhodecode:templates/admin/users/user_edit.mako') |
|
594 | 596 | def user_force_password_reset(self): |
|
595 | 597 | """ |
|
596 | 598 | toggle reset password flag for this user |
|
597 | 599 | """ |
|
598 | 600 | _ = self.request.translate |
|
599 | 601 | c = self.load_default_context() |
|
600 | 602 | |
|
601 | 603 | user_id = self.db_user_id |
|
602 | 604 | c.user = self.db_user |
|
603 | 605 | |
|
604 | 606 | try: |
|
605 | 607 | old_value = c.user.user_data.get('force_password_change') |
|
606 | 608 | c.user.update_userdata(force_password_change=not old_value) |
|
607 | 609 | |
|
608 | 610 | if old_value: |
|
609 | 611 | msg = _('Force password change disabled for user') |
|
610 | 612 | audit_logger.store_web( |
|
611 | 613 | 'user.edit.password_reset.disabled', |
|
612 | 614 | user=c.rhodecode_user) |
|
613 | 615 | else: |
|
614 | 616 | msg = _('Force password change enabled for user') |
|
615 | 617 | audit_logger.store_web( |
|
616 | 618 | 'user.edit.password_reset.enabled', |
|
617 | 619 | user=c.rhodecode_user) |
|
618 | 620 | |
|
619 | 621 | Session().commit() |
|
620 | 622 | h.flash(msg, category='success') |
|
621 | 623 | except Exception: |
|
622 | 624 | log.exception("Exception during password reset for user") |
|
623 | 625 | h.flash(_('An error occurred during password reset for user'), |
|
624 | 626 | category='error') |
|
625 | 627 | |
|
626 | 628 | raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id)) |
|
627 | 629 | |
|
628 | 630 | @LoginRequired() |
|
629 | 631 | @HasPermissionAllDecorator('hg.admin') |
|
630 | 632 | @CSRFRequired() |
|
631 | 633 | @view_config( |
|
632 | 634 | route_name='user_create_personal_repo_group', request_method='POST', |
|
633 | 635 | renderer='rhodecode:templates/admin/users/user_edit.mako') |
|
634 | 636 | def user_create_personal_repo_group(self): |
|
635 | 637 | """ |
|
636 | 638 | Create personal repository group for this user |
|
637 | 639 | """ |
|
638 | 640 | from rhodecode.model.repo_group import RepoGroupModel |
|
639 | 641 | |
|
640 | 642 | _ = self.request.translate |
|
641 | 643 | c = self.load_default_context() |
|
642 | 644 | |
|
643 | 645 | user_id = self.db_user_id |
|
644 | 646 | c.user = self.db_user |
|
645 | 647 | |
|
646 | 648 | personal_repo_group = RepoGroup.get_user_personal_repo_group( |
|
647 | 649 | c.user.user_id) |
|
648 | 650 | if personal_repo_group: |
|
649 | 651 | raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id)) |
|
650 | 652 | |
|
651 | 653 | personal_repo_group_name = RepoGroupModel().get_personal_group_name( |
|
652 | 654 | c.user) |
|
653 | 655 | named_personal_group = RepoGroup.get_by_group_name( |
|
654 | 656 | personal_repo_group_name) |
|
655 | 657 | try: |
|
656 | 658 | |
|
657 | 659 | if named_personal_group and named_personal_group.user_id == c.user.user_id: |
|
658 | 660 | # migrate the same named group, and mark it as personal |
|
659 | 661 | named_personal_group.personal = True |
|
660 | 662 | Session().add(named_personal_group) |
|
661 | 663 | Session().commit() |
|
662 | 664 | msg = _('Linked repository group `%s` as personal' % ( |
|
663 | 665 | personal_repo_group_name,)) |
|
664 | 666 | h.flash(msg, category='success') |
|
665 | 667 | elif not named_personal_group: |
|
666 | 668 | RepoGroupModel().create_personal_repo_group(c.user) |
|
667 | 669 | |
|
668 | 670 | msg = _('Created repository group `%s`' % ( |
|
669 | 671 | personal_repo_group_name,)) |
|
670 | 672 | h.flash(msg, category='success') |
|
671 | 673 | else: |
|
672 | 674 | msg = _('Repository group `%s` is already taken' % ( |
|
673 | 675 | personal_repo_group_name,)) |
|
674 | 676 | h.flash(msg, category='warning') |
|
675 | 677 | except Exception: |
|
676 | 678 | log.exception("Exception during repository group creation") |
|
677 | 679 | msg = _( |
|
678 | 680 | 'An error occurred during repository group creation for user') |
|
679 | 681 | h.flash(msg, category='error') |
|
680 | 682 | Session().rollback() |
|
681 | 683 | |
|
682 | 684 | raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id)) |
|
683 | 685 | |
|
684 | 686 | @LoginRequired() |
|
685 | 687 | @HasPermissionAllDecorator('hg.admin') |
|
686 | 688 | @view_config( |
|
687 | 689 | route_name='edit_user_auth_tokens', request_method='GET', |
|
688 | 690 | renderer='rhodecode:templates/admin/users/user_edit.mako') |
|
689 | 691 | def auth_tokens(self): |
|
690 | 692 | _ = self.request.translate |
|
691 | 693 | c = self.load_default_context() |
|
692 | 694 | c.user = self.db_user |
|
693 | 695 | |
|
694 | 696 | c.active = 'auth_tokens' |
|
695 | 697 | |
|
696 | 698 | c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_) |
|
697 | 699 | c.role_values = [ |
|
698 | 700 | (x, AuthTokenModel.cls._get_role_name(x)) |
|
699 | 701 | for x in AuthTokenModel.cls.ROLES] |
|
700 | 702 | c.role_options = [(c.role_values, _("Role"))] |
|
701 | 703 | c.user_auth_tokens = AuthTokenModel().get_auth_tokens( |
|
702 | 704 | c.user.user_id, show_expired=True) |
|
703 | 705 | c.role_vcs = AuthTokenModel.cls.ROLE_VCS |
|
704 | 706 | return self._get_template_context(c) |
|
705 | 707 | |
|
706 | 708 | def maybe_attach_token_scope(self, token): |
|
707 | 709 | # implemented in EE edition |
|
708 | 710 | pass |
|
709 | 711 | |
|
710 | 712 | @LoginRequired() |
|
711 | 713 | @HasPermissionAllDecorator('hg.admin') |
|
712 | 714 | @CSRFRequired() |
|
713 | 715 | @view_config( |
|
714 | 716 | route_name='edit_user_auth_tokens_add', request_method='POST') |
|
715 | 717 | def auth_tokens_add(self): |
|
716 | 718 | _ = self.request.translate |
|
717 | 719 | c = self.load_default_context() |
|
718 | 720 | |
|
719 | 721 | user_id = self.db_user_id |
|
720 | 722 | c.user = self.db_user |
|
721 | 723 | |
|
722 | 724 | user_data = c.user.get_api_data() |
|
723 | 725 | lifetime = safe_int(self.request.POST.get('lifetime'), -1) |
|
724 | 726 | description = self.request.POST.get('description') |
|
725 | 727 | role = self.request.POST.get('role') |
|
726 | 728 | |
|
727 | 729 | token = AuthTokenModel().create( |
|
728 | 730 | c.user.user_id, description, lifetime, role) |
|
729 | 731 | token_data = token.get_api_data() |
|
730 | 732 | |
|
731 | 733 | self.maybe_attach_token_scope(token) |
|
732 | 734 | audit_logger.store_web( |
|
733 | 735 | 'user.edit.token.add', action_data={ |
|
734 | 736 | 'data': {'token': token_data, 'user': user_data}}, |
|
735 | 737 | user=self._rhodecode_user, ) |
|
736 | 738 | Session().commit() |
|
737 | 739 | |
|
738 | 740 | h.flash(_("Auth token successfully created"), category='success') |
|
739 | 741 | return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id)) |
|
740 | 742 | |
|
741 | 743 | @LoginRequired() |
|
742 | 744 | @HasPermissionAllDecorator('hg.admin') |
|
743 | 745 | @CSRFRequired() |
|
744 | 746 | @view_config( |
|
745 | 747 | route_name='edit_user_auth_tokens_delete', request_method='POST') |
|
746 | 748 | def auth_tokens_delete(self): |
|
747 | 749 | _ = self.request.translate |
|
748 | 750 | c = self.load_default_context() |
|
749 | 751 | |
|
750 | 752 | user_id = self.db_user_id |
|
751 | 753 | c.user = self.db_user |
|
752 | 754 | |
|
753 | 755 | user_data = c.user.get_api_data() |
|
754 | 756 | |
|
755 | 757 | del_auth_token = self.request.POST.get('del_auth_token') |
|
756 | 758 | |
|
757 | 759 | if del_auth_token: |
|
758 | 760 | token = UserApiKeys.get_or_404(del_auth_token) |
|
759 | 761 | token_data = token.get_api_data() |
|
760 | 762 | |
|
761 | 763 | AuthTokenModel().delete(del_auth_token, c.user.user_id) |
|
762 | 764 | audit_logger.store_web( |
|
763 | 765 | 'user.edit.token.delete', action_data={ |
|
764 | 766 | 'data': {'token': token_data, 'user': user_data}}, |
|
765 | 767 | user=self._rhodecode_user,) |
|
766 | 768 | Session().commit() |
|
767 | 769 | h.flash(_("Auth token successfully deleted"), category='success') |
|
768 | 770 | |
|
769 | 771 | return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id)) |
|
770 | 772 | |
|
771 | 773 | @LoginRequired() |
|
772 | 774 | @HasPermissionAllDecorator('hg.admin') |
|
773 | 775 | @view_config( |
|
774 | 776 | route_name='edit_user_ssh_keys', request_method='GET', |
|
775 | 777 | renderer='rhodecode:templates/admin/users/user_edit.mako') |
|
776 | 778 | def ssh_keys(self): |
|
777 | 779 | _ = self.request.translate |
|
778 | 780 | c = self.load_default_context() |
|
779 | 781 | c.user = self.db_user |
|
780 | 782 | |
|
781 | 783 | c.active = 'ssh_keys' |
|
782 | 784 | c.default_key = self.request.GET.get('default_key') |
|
783 | 785 | c.user_ssh_keys = SshKeyModel().get_ssh_keys(c.user.user_id) |
|
784 | 786 | return self._get_template_context(c) |
|
785 | 787 | |
|
786 | 788 | @LoginRequired() |
|
787 | 789 | @HasPermissionAllDecorator('hg.admin') |
|
788 | 790 | @view_config( |
|
789 | 791 | route_name='edit_user_ssh_keys_generate_keypair', request_method='GET', |
|
790 | 792 | renderer='rhodecode:templates/admin/users/user_edit.mako') |
|
791 | 793 | def ssh_keys_generate_keypair(self): |
|
792 | 794 | _ = self.request.translate |
|
793 | 795 | c = self.load_default_context() |
|
794 | 796 | |
|
795 | 797 | c.user = self.db_user |
|
796 | 798 | |
|
797 | 799 | c.active = 'ssh_keys_generate' |
|
798 | 800 | comment = 'RhodeCode-SSH {}'.format(c.user.email or '') |
|
799 | 801 | c.private, c.public = SshKeyModel().generate_keypair(comment=comment) |
|
800 | 802 | |
|
801 | 803 | return self._get_template_context(c) |
|
802 | 804 | |
|
803 | 805 | @LoginRequired() |
|
804 | 806 | @HasPermissionAllDecorator('hg.admin') |
|
805 | 807 | @CSRFRequired() |
|
806 | 808 | @view_config( |
|
807 | 809 | route_name='edit_user_ssh_keys_add', request_method='POST') |
|
808 | 810 | def ssh_keys_add(self): |
|
809 | 811 | _ = self.request.translate |
|
810 | 812 | c = self.load_default_context() |
|
811 | 813 | |
|
812 | 814 | user_id = self.db_user_id |
|
813 | 815 | c.user = self.db_user |
|
814 | 816 | |
|
815 | 817 | user_data = c.user.get_api_data() |
|
816 | 818 | key_data = self.request.POST.get('key_data') |
|
817 | 819 | description = self.request.POST.get('description') |
|
818 | 820 | |
|
819 | 821 | try: |
|
820 | 822 | if not key_data: |
|
821 | 823 | raise ValueError('Please add a valid public key') |
|
822 | 824 | |
|
823 | 825 | key = SshKeyModel().parse_key(key_data.strip()) |
|
824 | 826 | fingerprint = key.hash_md5() |
|
825 | 827 | |
|
826 | 828 | ssh_key = SshKeyModel().create( |
|
827 | 829 | c.user.user_id, fingerprint, key_data, description) |
|
828 | 830 | ssh_key_data = ssh_key.get_api_data() |
|
829 | 831 | |
|
830 | 832 | audit_logger.store_web( |
|
831 | 833 | 'user.edit.ssh_key.add', action_data={ |
|
832 | 834 | 'data': {'ssh_key': ssh_key_data, 'user': user_data}}, |
|
833 | 835 | user=self._rhodecode_user, ) |
|
834 | 836 | Session().commit() |
|
835 | 837 | |
|
836 | 838 | # Trigger an event on change of keys. |
|
837 | 839 | trigger(SshKeyFileChangeEvent(), self.request.registry) |
|
838 | 840 | |
|
839 | 841 | h.flash(_("Ssh Key successfully created"), category='success') |
|
840 | 842 | |
|
841 | 843 | except IntegrityError: |
|
842 | 844 | log.exception("Exception during ssh key saving") |
|
843 | 845 | h.flash(_('An error occurred during ssh key saving: {}').format( |
|
844 | 846 | 'Such key already exists, please use a different one'), |
|
845 | 847 | category='error') |
|
846 | 848 | except Exception as e: |
|
847 | 849 | log.exception("Exception during ssh key saving") |
|
848 | 850 | h.flash(_('An error occurred during ssh key saving: {}').format(e), |
|
849 | 851 | category='error') |
|
850 | 852 | |
|
851 | 853 | return HTTPFound( |
|
852 | 854 | h.route_path('edit_user_ssh_keys', user_id=user_id)) |
|
853 | 855 | |
|
854 | 856 | @LoginRequired() |
|
855 | 857 | @HasPermissionAllDecorator('hg.admin') |
|
856 | 858 | @CSRFRequired() |
|
857 | 859 | @view_config( |
|
858 | 860 | route_name='edit_user_ssh_keys_delete', request_method='POST') |
|
859 | 861 | def ssh_keys_delete(self): |
|
860 | 862 | _ = self.request.translate |
|
861 | 863 | c = self.load_default_context() |
|
862 | 864 | |
|
863 | 865 | user_id = self.db_user_id |
|
864 | 866 | c.user = self.db_user |
|
865 | 867 | |
|
866 | 868 | user_data = c.user.get_api_data() |
|
867 | 869 | |
|
868 | 870 | del_ssh_key = self.request.POST.get('del_ssh_key') |
|
869 | 871 | |
|
870 | 872 | if del_ssh_key: |
|
871 | 873 | ssh_key = UserSshKeys.get_or_404(del_ssh_key) |
|
872 | 874 | ssh_key_data = ssh_key.get_api_data() |
|
873 | 875 | |
|
874 | 876 | SshKeyModel().delete(del_ssh_key, c.user.user_id) |
|
875 | 877 | audit_logger.store_web( |
|
876 | 878 | 'user.edit.ssh_key.delete', action_data={ |
|
877 | 879 | 'data': {'ssh_key': ssh_key_data, 'user': user_data}}, |
|
878 | 880 | user=self._rhodecode_user,) |
|
879 | 881 | Session().commit() |
|
880 | 882 | # Trigger an event on change of keys. |
|
881 | 883 | trigger(SshKeyFileChangeEvent(), self.request.registry) |
|
882 | 884 | h.flash(_("Ssh key successfully deleted"), category='success') |
|
883 | 885 | |
|
884 | 886 | return HTTPFound(h.route_path('edit_user_ssh_keys', user_id=user_id)) |
|
885 | 887 | |
|
886 | 888 | @LoginRequired() |
|
887 | 889 | @HasPermissionAllDecorator('hg.admin') |
|
888 | 890 | @view_config( |
|
889 | 891 | route_name='edit_user_emails', request_method='GET', |
|
890 | 892 | renderer='rhodecode:templates/admin/users/user_edit.mako') |
|
891 | 893 | def emails(self): |
|
892 | 894 | _ = self.request.translate |
|
893 | 895 | c = self.load_default_context() |
|
894 | 896 | c.user = self.db_user |
|
895 | 897 | |
|
896 | 898 | c.active = 'emails' |
|
897 | 899 | c.user_email_map = UserEmailMap.query() \ |
|
898 | 900 | .filter(UserEmailMap.user == c.user).all() |
|
899 | 901 | |
|
900 | 902 | return self._get_template_context(c) |
|
901 | 903 | |
|
902 | 904 | @LoginRequired() |
|
903 | 905 | @HasPermissionAllDecorator('hg.admin') |
|
904 | 906 | @CSRFRequired() |
|
905 | 907 | @view_config( |
|
906 | 908 | route_name='edit_user_emails_add', request_method='POST') |
|
907 | 909 | def emails_add(self): |
|
908 | 910 | _ = self.request.translate |
|
909 | 911 | c = self.load_default_context() |
|
910 | 912 | |
|
911 | 913 | user_id = self.db_user_id |
|
912 | 914 | c.user = self.db_user |
|
913 | 915 | |
|
914 | 916 | email = self.request.POST.get('new_email') |
|
915 | 917 | user_data = c.user.get_api_data() |
|
916 | 918 | try: |
|
919 | ||
|
920 | form = UserExtraEmailForm(self.request.translate)() | |
|
921 | data = form.to_python({'email': email}) | |
|
922 | email = data['email'] | |
|
923 | ||
|
917 | 924 | UserModel().add_extra_email(c.user.user_id, email) |
|
918 | 925 | audit_logger.store_web( |
|
919 | 926 | 'user.edit.email.add', |
|
920 | 927 | action_data={'email': email, 'user': user_data}, |
|
921 | 928 | user=self._rhodecode_user) |
|
922 | 929 | Session().commit() |
|
923 | 930 | h.flash(_("Added new email address `%s` for user account") % email, |
|
924 | 931 | category='success') |
|
925 | 932 | except formencode.Invalid as error: |
|
926 | 933 | h.flash(h.escape(error.error_dict['email']), category='error') |
|
927 | 934 | except IntegrityError: |
|
928 | 935 | log.warning("Email %s already exists", email) |
|
929 | 936 | h.flash(_('Email `{}` is already registered for another user.').format(email), |
|
930 | 937 | category='error') |
|
931 | 938 | except Exception: |
|
932 | 939 | log.exception("Exception during email saving") |
|
933 | 940 | h.flash(_('An error occurred during email saving'), |
|
934 | 941 | category='error') |
|
935 | 942 | raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id)) |
|
936 | 943 | |
|
937 | 944 | @LoginRequired() |
|
938 | 945 | @HasPermissionAllDecorator('hg.admin') |
|
939 | 946 | @CSRFRequired() |
|
940 | 947 | @view_config( |
|
941 | 948 | route_name='edit_user_emails_delete', request_method='POST') |
|
942 | 949 | def emails_delete(self): |
|
943 | 950 | _ = self.request.translate |
|
944 | 951 | c = self.load_default_context() |
|
945 | 952 | |
|
946 | 953 | user_id = self.db_user_id |
|
947 | 954 | c.user = self.db_user |
|
948 | 955 | |
|
949 | 956 | email_id = self.request.POST.get('del_email_id') |
|
950 | 957 | user_model = UserModel() |
|
951 | 958 | |
|
952 | 959 | email = UserEmailMap.query().get(email_id).email |
|
953 | 960 | user_data = c.user.get_api_data() |
|
954 | 961 | user_model.delete_extra_email(c.user.user_id, email_id) |
|
955 | 962 | audit_logger.store_web( |
|
956 | 963 | 'user.edit.email.delete', |
|
957 | 964 | action_data={'email': email, 'user': user_data}, |
|
958 | 965 | user=self._rhodecode_user) |
|
959 | 966 | Session().commit() |
|
960 | 967 | h.flash(_("Removed email address from user account"), |
|
961 | 968 | category='success') |
|
962 | 969 | raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id)) |
|
963 | 970 | |
|
964 | 971 | @LoginRequired() |
|
965 | 972 | @HasPermissionAllDecorator('hg.admin') |
|
966 | 973 | @view_config( |
|
967 | 974 | route_name='edit_user_ips', request_method='GET', |
|
968 | 975 | renderer='rhodecode:templates/admin/users/user_edit.mako') |
|
969 | 976 | def ips(self): |
|
970 | 977 | _ = self.request.translate |
|
971 | 978 | c = self.load_default_context() |
|
972 | 979 | c.user = self.db_user |
|
973 | 980 | |
|
974 | 981 | c.active = 'ips' |
|
975 | 982 | c.user_ip_map = UserIpMap.query() \ |
|
976 | 983 | .filter(UserIpMap.user == c.user).all() |
|
977 | 984 | |
|
978 | 985 | c.inherit_default_ips = c.user.inherit_default_permissions |
|
979 | 986 | c.default_user_ip_map = UserIpMap.query() \ |
|
980 | 987 | .filter(UserIpMap.user == User.get_default_user()).all() |
|
981 | 988 | |
|
982 | 989 | return self._get_template_context(c) |
|
983 | 990 | |
|
984 | 991 | @LoginRequired() |
|
985 | 992 | @HasPermissionAllDecorator('hg.admin') |
|
986 | 993 | @CSRFRequired() |
|
987 | 994 | @view_config( |
|
988 | 995 | route_name='edit_user_ips_add', request_method='POST') |
|
989 | 996 | # NOTE(marcink): this view is allowed for default users, as we can |
|
990 | 997 | # edit their IP white list |
|
991 | 998 | def ips_add(self): |
|
992 | 999 | _ = self.request.translate |
|
993 | 1000 | c = self.load_default_context() |
|
994 | 1001 | |
|
995 | 1002 | user_id = self.db_user_id |
|
996 | 1003 | c.user = self.db_user |
|
997 | 1004 | |
|
998 | 1005 | user_model = UserModel() |
|
999 | 1006 | desc = self.request.POST.get('description') |
|
1000 | 1007 | try: |
|
1001 | 1008 | ip_list = user_model.parse_ip_range( |
|
1002 | 1009 | self.request.POST.get('new_ip')) |
|
1003 | 1010 | except Exception as e: |
|
1004 | 1011 | ip_list = [] |
|
1005 | 1012 | log.exception("Exception during ip saving") |
|
1006 | 1013 | h.flash(_('An error occurred during ip saving:%s' % (e,)), |
|
1007 | 1014 | category='error') |
|
1008 | 1015 | added = [] |
|
1009 | 1016 | user_data = c.user.get_api_data() |
|
1010 | 1017 | for ip in ip_list: |
|
1011 | 1018 | try: |
|
1019 | form = UserExtraIpForm(self.request.translate)() | |
|
1020 | data = form.to_python({'ip': ip}) | |
|
1021 | ip = data['ip'] | |
|
1022 | ||
|
1012 | 1023 | user_model.add_extra_ip(c.user.user_id, ip, desc) |
|
1013 | 1024 | audit_logger.store_web( |
|
1014 | 1025 | 'user.edit.ip.add', |
|
1015 | 1026 | action_data={'ip': ip, 'user': user_data}, |
|
1016 | 1027 | user=self._rhodecode_user) |
|
1017 | 1028 | Session().commit() |
|
1018 | 1029 | added.append(ip) |
|
1019 | 1030 | except formencode.Invalid as error: |
|
1020 | 1031 | msg = error.error_dict['ip'] |
|
1021 | 1032 | h.flash(msg, category='error') |
|
1022 | 1033 | except Exception: |
|
1023 | 1034 | log.exception("Exception during ip saving") |
|
1024 | 1035 | h.flash(_('An error occurred during ip saving'), |
|
1025 | 1036 | category='error') |
|
1026 | 1037 | if added: |
|
1027 | 1038 | h.flash( |
|
1028 | 1039 | _("Added ips %s to user whitelist") % (', '.join(ip_list), ), |
|
1029 | 1040 | category='success') |
|
1030 | 1041 | if 'default_user' in self.request.POST: |
|
1031 | 1042 | # case for editing global IP list we do it for 'DEFAULT' user |
|
1032 | 1043 | raise HTTPFound(h.route_path('admin_permissions_ips')) |
|
1033 | 1044 | raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id)) |
|
1034 | 1045 | |
|
1035 | 1046 | @LoginRequired() |
|
1036 | 1047 | @HasPermissionAllDecorator('hg.admin') |
|
1037 | 1048 | @CSRFRequired() |
|
1038 | 1049 | @view_config( |
|
1039 | 1050 | route_name='edit_user_ips_delete', request_method='POST') |
|
1040 | 1051 | # NOTE(marcink): this view is allowed for default users, as we can |
|
1041 | 1052 | # edit their IP white list |
|
1042 | 1053 | def ips_delete(self): |
|
1043 | 1054 | _ = self.request.translate |
|
1044 | 1055 | c = self.load_default_context() |
|
1045 | 1056 | |
|
1046 | 1057 | user_id = self.db_user_id |
|
1047 | 1058 | c.user = self.db_user |
|
1048 | 1059 | |
|
1049 | 1060 | ip_id = self.request.POST.get('del_ip_id') |
|
1050 | 1061 | user_model = UserModel() |
|
1051 | 1062 | user_data = c.user.get_api_data() |
|
1052 | 1063 | ip = UserIpMap.query().get(ip_id).ip_addr |
|
1053 | 1064 | user_model.delete_extra_ip(c.user.user_id, ip_id) |
|
1054 | 1065 | audit_logger.store_web( |
|
1055 | 1066 | 'user.edit.ip.delete', action_data={'ip': ip, 'user': user_data}, |
|
1056 | 1067 | user=self._rhodecode_user) |
|
1057 | 1068 | Session().commit() |
|
1058 | 1069 | h.flash(_("Removed ip address from user whitelist"), category='success') |
|
1059 | 1070 | |
|
1060 | 1071 | if 'default_user' in self.request.POST: |
|
1061 | 1072 | # case for editing global IP list we do it for 'DEFAULT' user |
|
1062 | 1073 | raise HTTPFound(h.route_path('admin_permissions_ips')) |
|
1063 | 1074 | raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id)) |
|
1064 | 1075 | |
|
1065 | 1076 | @LoginRequired() |
|
1066 | 1077 | @HasPermissionAllDecorator('hg.admin') |
|
1067 | 1078 | @view_config( |
|
1068 | 1079 | route_name='edit_user_groups_management', request_method='GET', |
|
1069 | 1080 | renderer='rhodecode:templates/admin/users/user_edit.mako') |
|
1070 | 1081 | def groups_management(self): |
|
1071 | 1082 | c = self.load_default_context() |
|
1072 | 1083 | c.user = self.db_user |
|
1073 | 1084 | c.data = c.user.group_member |
|
1074 | 1085 | |
|
1075 | 1086 | groups = [UserGroupModel.get_user_groups_as_dict(group.users_group) |
|
1076 | 1087 | for group in c.user.group_member] |
|
1077 | 1088 | c.groups = json.dumps(groups) |
|
1078 | 1089 | c.active = 'groups' |
|
1079 | 1090 | |
|
1080 | 1091 | return self._get_template_context(c) |
|
1081 | 1092 | |
|
1082 | 1093 | @LoginRequired() |
|
1083 | 1094 | @HasPermissionAllDecorator('hg.admin') |
|
1084 | 1095 | @CSRFRequired() |
|
1085 | 1096 | @view_config( |
|
1086 | 1097 | route_name='edit_user_groups_management_updates', request_method='POST') |
|
1087 | 1098 | def groups_management_updates(self): |
|
1088 | 1099 | _ = self.request.translate |
|
1089 | 1100 | c = self.load_default_context() |
|
1090 | 1101 | |
|
1091 | 1102 | user_id = self.db_user_id |
|
1092 | 1103 | c.user = self.db_user |
|
1093 | 1104 | |
|
1094 | 1105 | user_groups = set(self.request.POST.getall('users_group_id')) |
|
1095 | 1106 | user_groups_objects = [] |
|
1096 | 1107 | |
|
1097 | 1108 | for ugid in user_groups: |
|
1098 | 1109 | user_groups_objects.append( |
|
1099 | 1110 | UserGroupModel().get_group(safe_int(ugid))) |
|
1100 | 1111 | user_group_model = UserGroupModel() |
|
1101 | 1112 | added_to_groups, removed_from_groups = \ |
|
1102 | 1113 | user_group_model.change_groups(c.user, user_groups_objects) |
|
1103 | 1114 | |
|
1104 | 1115 | user_data = c.user.get_api_data() |
|
1105 | 1116 | for user_group_id in added_to_groups: |
|
1106 | 1117 | user_group = UserGroup.get(user_group_id) |
|
1107 | 1118 | old_values = user_group.get_api_data() |
|
1108 | 1119 | audit_logger.store_web( |
|
1109 | 1120 | 'user_group.edit.member.add', |
|
1110 | 1121 | action_data={'user': user_data, 'old_data': old_values}, |
|
1111 | 1122 | user=self._rhodecode_user) |
|
1112 | 1123 | |
|
1113 | 1124 | for user_group_id in removed_from_groups: |
|
1114 | 1125 | user_group = UserGroup.get(user_group_id) |
|
1115 | 1126 | old_values = user_group.get_api_data() |
|
1116 | 1127 | audit_logger.store_web( |
|
1117 | 1128 | 'user_group.edit.member.delete', |
|
1118 | 1129 | action_data={'user': user_data, 'old_data': old_values}, |
|
1119 | 1130 | user=self._rhodecode_user) |
|
1120 | 1131 | |
|
1121 | 1132 | Session().commit() |
|
1122 | 1133 | c.active = 'user_groups_management' |
|
1123 | 1134 | h.flash(_("Groups successfully changed"), category='success') |
|
1124 | 1135 | |
|
1125 | 1136 | return HTTPFound(h.route_path( |
|
1126 | 1137 | 'edit_user_groups_management', user_id=user_id)) |
|
1127 | 1138 | |
|
1128 | 1139 | @LoginRequired() |
|
1129 | 1140 | @HasPermissionAllDecorator('hg.admin') |
|
1130 | 1141 | @view_config( |
|
1131 | 1142 | route_name='edit_user_audit_logs', request_method='GET', |
|
1132 | 1143 | renderer='rhodecode:templates/admin/users/user_edit.mako') |
|
1133 | 1144 | def user_audit_logs(self): |
|
1134 | 1145 | _ = self.request.translate |
|
1135 | 1146 | c = self.load_default_context() |
|
1136 | 1147 | c.user = self.db_user |
|
1137 | 1148 | |
|
1138 | 1149 | c.active = 'audit' |
|
1139 | 1150 | |
|
1140 | 1151 | p = safe_int(self.request.GET.get('page', 1), 1) |
|
1141 | 1152 | |
|
1142 | 1153 | filter_term = self.request.GET.get('filter') |
|
1143 | 1154 | user_log = UserModel().get_user_log(c.user, filter_term) |
|
1144 | 1155 | |
|
1145 | 1156 | def url_generator(**kw): |
|
1146 | 1157 | if filter_term: |
|
1147 | 1158 | kw['filter'] = filter_term |
|
1148 | 1159 | return self.request.current_route_path(_query=kw) |
|
1149 | 1160 | |
|
1150 | 1161 | c.audit_logs = h.Page( |
|
1151 | 1162 | user_log, page=p, items_per_page=10, url=url_generator) |
|
1152 | 1163 | c.filter_term = filter_term |
|
1153 | 1164 | return self._get_template_context(c) |
|
1154 | 1165 | |
|
1155 | 1166 | @LoginRequired() |
|
1156 | 1167 | @HasPermissionAllDecorator('hg.admin') |
|
1157 | 1168 | @view_config( |
|
1158 | 1169 | route_name='edit_user_perms_summary', request_method='GET', |
|
1159 | 1170 | renderer='rhodecode:templates/admin/users/user_edit.mako') |
|
1160 | 1171 | def user_perms_summary(self): |
|
1161 | 1172 | _ = self.request.translate |
|
1162 | 1173 | c = self.load_default_context() |
|
1163 | 1174 | c.user = self.db_user |
|
1164 | 1175 | |
|
1165 | 1176 | c.active = 'perms_summary' |
|
1166 | 1177 | c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr) |
|
1167 | 1178 | |
|
1168 | 1179 | return self._get_template_context(c) |
|
1169 | 1180 | |
|
1170 | 1181 | @LoginRequired() |
|
1171 | 1182 | @HasPermissionAllDecorator('hg.admin') |
|
1172 | 1183 | @view_config( |
|
1173 | 1184 | route_name='edit_user_perms_summary_json', request_method='GET', |
|
1174 | 1185 | renderer='json_ext') |
|
1175 | 1186 | def user_perms_summary_json(self): |
|
1176 | 1187 | self.load_default_context() |
|
1177 | 1188 | perm_user = self.db_user.AuthUser(ip_addr=self.request.remote_addr) |
|
1178 | 1189 | |
|
1179 | 1190 | return perm_user.permissions |
@@ -1,167 +1,169 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | |
|
3 | 3 | # Copyright (C) 2010-2017 RhodeCode GmbH |
|
4 | 4 | # |
|
5 | 5 | # This program is free software: you can redistribute it and/or modify |
|
6 | 6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
7 | 7 | # (only), as published by the Free Software Foundation. |
|
8 | 8 | # |
|
9 | 9 | # This program is distributed in the hope that it will be useful, |
|
10 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 | 12 | # GNU General Public License for more details. |
|
13 | 13 | # |
|
14 | 14 | # You should have received a copy of the GNU Affero General Public License |
|
15 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | 16 | # |
|
17 | 17 | # This program is dual-licensed. If you wish to learn more about the |
|
18 | 18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
19 | 19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
20 | 20 | |
|
21 | 21 | import logging |
|
22 | 22 | import uuid |
|
23 | 23 | |
|
24 | 24 | from pyramid.view import view_config |
|
25 | 25 | from pyramid.httpexceptions import HTTPBadRequest, HTTPForbidden, HTTPBadGateway |
|
26 | 26 | |
|
27 | from rhodecode.apps._base import BaseAppView | |
|
27 | 28 | from rhodecode.lib.channelstream import ( |
|
28 | 29 | channelstream_request, |
|
29 | 30 | ChannelstreamConnectionException, |
|
30 | 31 | ChannelstreamPermissionException, |
|
31 | 32 | check_channel_permissions, |
|
32 | 33 | get_connection_validators, |
|
33 | 34 | get_user_data, |
|
34 | 35 | parse_channels_info, |
|
35 | 36 | update_history_from_logs, |
|
36 | 37 | STATE_PUBLIC_KEYS) |
|
38 | ||
|
37 | 39 | from rhodecode.lib.auth import NotAnonymous |
|
38 | 40 | |
|
39 | 41 | log = logging.getLogger(__name__) |
|
40 | 42 | |
|
41 | 43 | |
|
42 |
class ChannelstreamView( |
|
|
43 | def __init__(self, context, request): | |
|
44 | self.context = context | |
|
45 | self.request = request | |
|
44 | class ChannelstreamView(BaseAppView): | |
|
46 | 45 | |
|
47 | # Some of the decorators rely on this attribute to be present | |
|
48 | # on the class of the decorated method. | |
|
49 | self._rhodecode_user = request.user | |
|
50 | registry = request.registry | |
|
51 | self.channelstream_config = registry.rhodecode_plugins['channelstream'] | |
|
46 | def load_default_context(self): | |
|
47 | c = self._get_local_tmpl_context() | |
|
48 | self.channelstream_config = \ | |
|
49 | self.request.registry.rhodecode_plugins['channelstream'] | |
|
52 | 50 | if not self.channelstream_config.get('enabled'): |
|
53 | 51 | log.error('Channelstream plugin is disabled') |
|
54 | 52 | raise HTTPBadRequest() |
|
55 | 53 | |
|
54 | return c | |
|
55 | ||
|
56 | 56 | @NotAnonymous() |
|
57 | @view_config(route_name='channelstream_connect', renderer='json') | |
|
57 | @view_config(route_name='channelstream_connect', renderer='json_ext') | |
|
58 | 58 | def connect(self): |
|
59 | self.load_default_context() | |
|
59 | 60 | """ handle authorization of users trying to connect """ |
|
60 | 61 | try: |
|
61 | 62 | json_body = self.request.json_body |
|
62 | 63 | except Exception: |
|
63 | 64 | log.exception('Failed to decode json from request') |
|
64 | 65 | raise HTTPBadRequest() |
|
65 | 66 | |
|
66 | 67 | try: |
|
67 | 68 | channels = check_channel_permissions( |
|
68 | 69 | json_body.get('channels'), |
|
69 | 70 | get_connection_validators(self.request.registry)) |
|
70 | 71 | except ChannelstreamPermissionException: |
|
71 | 72 | log.error('Incorrect permissions for requested channels') |
|
72 | 73 | raise HTTPForbidden() |
|
73 | 74 | |
|
74 | 75 | user = self._rhodecode_user |
|
75 | 76 | if user.user_id: |
|
76 | 77 | user_data = get_user_data(user.user_id) |
|
77 | 78 | else: |
|
78 | 79 | user_data = { |
|
79 | 80 | 'id': None, |
|
80 | 81 | 'username': None, |
|
81 | 82 | 'first_name': None, |
|
82 | 83 | 'last_name': None, |
|
83 | 84 | 'icon_link': None, |
|
84 | 85 | 'display_name': None, |
|
85 | 86 | 'display_link': None, |
|
86 | 87 | } |
|
87 | 88 | user_data['permissions'] = self._rhodecode_user.permissions_safe |
|
88 | 89 | payload = { |
|
89 | 90 | 'username': user.username, |
|
90 | 91 | 'user_state': user_data, |
|
91 | 92 | 'conn_id': str(uuid.uuid4()), |
|
92 | 93 | 'channels': channels, |
|
93 | 94 | 'channel_configs': {}, |
|
94 | 95 | 'state_public_keys': STATE_PUBLIC_KEYS, |
|
95 | 96 | 'info': { |
|
96 | 97 | 'exclude_channels': ['broadcast'] |
|
97 | 98 | } |
|
98 | 99 | } |
|
99 | 100 | filtered_channels = [channel for channel in channels |
|
100 | 101 | if channel != 'broadcast'] |
|
101 | 102 | for channel in filtered_channels: |
|
102 | 103 | payload['channel_configs'][channel] = { |
|
103 | 104 | 'notify_presence': True, |
|
104 | 105 | 'history_size': 100, |
|
105 | 106 | 'store_history': True, |
|
106 | 107 | 'broadcast_presence_with_user_lists': True |
|
107 | 108 | } |
|
108 | 109 | # connect user to server |
|
109 | 110 | try: |
|
110 | 111 | connect_result = channelstream_request(self.channelstream_config, |
|
111 | 112 | payload, '/connect') |
|
112 | 113 | except ChannelstreamConnectionException: |
|
113 | 114 | log.exception('Channelstream service is down') |
|
114 | 115 | return HTTPBadGateway() |
|
115 | 116 | |
|
116 | 117 | connect_result['channels'] = channels |
|
117 | 118 | connect_result['channels_info'] = parse_channels_info( |
|
118 | 119 | connect_result['channels_info'], |
|
119 | 120 | include_channel_info=filtered_channels) |
|
120 | 121 | update_history_from_logs(self.channelstream_config, |
|
121 | 122 | filtered_channels, connect_result) |
|
122 | 123 | return connect_result |
|
123 | 124 | |
|
124 | 125 | @NotAnonymous() |
|
125 | @view_config(route_name='channelstream_subscribe', renderer='json') | |
|
126 | @view_config(route_name='channelstream_subscribe', renderer='json_ext') | |
|
126 | 127 | def subscribe(self): |
|
127 | 128 | """ can be used to subscribe specific connection to other channels """ |
|
129 | self.load_default_context() | |
|
128 | 130 | try: |
|
129 | 131 | json_body = self.request.json_body |
|
130 | 132 | except Exception: |
|
131 | 133 | log.exception('Failed to decode json from request') |
|
132 | 134 | raise HTTPBadRequest() |
|
133 | 135 | try: |
|
134 | 136 | channels = check_channel_permissions( |
|
135 | 137 | json_body.get('channels'), |
|
136 | 138 | get_connection_validators(self.request.registry)) |
|
137 | 139 | except ChannelstreamPermissionException: |
|
138 | 140 | log.error('Incorrect permissions for requested channels') |
|
139 | 141 | raise HTTPForbidden() |
|
140 | 142 | payload = {'conn_id': json_body.get('conn_id', ''), |
|
141 | 143 | 'channels': channels, |
|
142 | 144 | 'channel_configs': {}, |
|
143 | 145 | 'info': { |
|
144 | 146 | 'exclude_channels': ['broadcast']} |
|
145 | 147 | } |
|
146 | 148 | filtered_channels = [chan for chan in channels if chan != 'broadcast'] |
|
147 | 149 | for channel in filtered_channels: |
|
148 | 150 | payload['channel_configs'][channel] = { |
|
149 | 151 | 'notify_presence': True, |
|
150 | 152 | 'history_size': 100, |
|
151 | 153 | 'store_history': True, |
|
152 | 154 | 'broadcast_presence_with_user_lists': True |
|
153 | 155 | } |
|
154 | 156 | try: |
|
155 | 157 | connect_result = channelstream_request( |
|
156 | 158 | self.channelstream_config, payload, '/subscribe') |
|
157 | 159 | except ChannelstreamConnectionException: |
|
158 | 160 | log.exception('Channelstream service is down') |
|
159 | 161 | return HTTPBadGateway() |
|
160 | 162 | # include_channel_info will limit history only to new channel |
|
161 | 163 | # to not overwrite histories on other channels in client |
|
162 | 164 | connect_result['channels_info'] = parse_channels_info( |
|
163 | 165 | connect_result['channels_info'], |
|
164 | 166 | include_channel_info=filtered_channels) |
|
165 | 167 | update_history_from_logs(self.channelstream_config, |
|
166 | 168 | filtered_channels, connect_result) |
|
167 | 169 | return connect_result |
@@ -1,59 +1,59 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | |
|
3 | 3 | # Copyright (C) 2016-2017 RhodeCode GmbH |
|
4 | 4 | # |
|
5 | 5 | # This program is free software: you can redistribute it and/or modify |
|
6 | 6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
7 | 7 | # (only), as published by the Free Software Foundation. |
|
8 | 8 | # |
|
9 | 9 | # This program is distributed in the hope that it will be useful, |
|
10 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 | 12 | # GNU General Public License for more details. |
|
13 | 13 | # |
|
14 | 14 | # You should have received a copy of the GNU Affero General Public License |
|
15 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | 16 | # |
|
17 | 17 | # This program is dual-licensed. If you wish to learn more about the |
|
18 | 18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
19 | 19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
20 | 20 | |
|
21 | 21 | import os |
|
22 | 22 | import logging |
|
23 | 23 | |
|
24 | 24 | from pyramid.view import view_config |
|
25 | 25 | from pyramid.renderers import render_to_response |
|
26 | 26 | from rhodecode.apps._base import BaseAppView |
|
27 | 27 | |
|
28 | 28 | log = logging.getLogger(__name__) |
|
29 | 29 | |
|
30 | 30 | |
|
31 | 31 | class DebugStyleView(BaseAppView): |
|
32 | 32 | def load_default_context(self): |
|
33 | 33 | c = self._get_local_tmpl_context() |
|
34 | self._register_global_c(c) | |
|
34 | ||
|
35 | 35 | return c |
|
36 | 36 | |
|
37 | 37 | @view_config( |
|
38 | 38 | route_name='debug_style_home', request_method='GET', |
|
39 | 39 | renderer=None) |
|
40 | 40 | def index(self): |
|
41 | 41 | c = self.load_default_context() |
|
42 | 42 | c.active = 'index' |
|
43 | 43 | |
|
44 | 44 | return render_to_response( |
|
45 | 45 | 'debug_style/index.html', self._get_template_context(c), |
|
46 | 46 | request=self.request) |
|
47 | 47 | |
|
48 | 48 | @view_config( |
|
49 | 49 | route_name='debug_style_template', request_method='GET', |
|
50 | 50 | renderer=None) |
|
51 | 51 | def template(self): |
|
52 | 52 | t_path = self.request.matchdict['t_path'] |
|
53 | 53 | c = self.load_default_context() |
|
54 | 54 | c.active = os.path.splitext(t_path)[0] |
|
55 | 55 | c.came_from = '' |
|
56 | 56 | |
|
57 | 57 | return render_to_response( |
|
58 | 58 | 'debug_style/' + t_path, self._get_template_context(c), |
|
59 | 59 | request=self.request) No newline at end of file |
@@ -1,413 +1,413 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | |
|
3 | 3 | # Copyright (C) 2013-2017 RhodeCode GmbH |
|
4 | 4 | # |
|
5 | 5 | # This program is free software: you can redistribute it and/or modify |
|
6 | 6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
7 | 7 | # (only), as published by the Free Software Foundation. |
|
8 | 8 | # |
|
9 | 9 | # This program is distributed in the hope that it will be useful, |
|
10 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 | 12 | # GNU General Public License for more details. |
|
13 | 13 | # |
|
14 | 14 | # You should have received a copy of the GNU Affero General Public License |
|
15 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | 16 | # |
|
17 | 17 | # This program is dual-licensed. If you wish to learn more about the |
|
18 | 18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
19 | 19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
20 | 20 | |
|
21 | 21 | import time |
|
22 | 22 | import logging |
|
23 | 23 | |
|
24 | 24 | import formencode |
|
25 | 25 | import formencode.htmlfill |
|
26 | 26 | import peppercorn |
|
27 | 27 | |
|
28 | 28 | from pyramid.httpexceptions import HTTPNotFound, HTTPFound |
|
29 | 29 | from pyramid.view import view_config |
|
30 | 30 | from pyramid.renderers import render |
|
31 | 31 | from pyramid.response import Response |
|
32 | 32 | |
|
33 | 33 | from rhodecode.apps._base import BaseAppView |
|
34 | 34 | from rhodecode.lib import helpers as h |
|
35 | 35 | from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired |
|
36 | 36 | from rhodecode.lib.utils2 import time_to_datetime |
|
37 | 37 | from rhodecode.lib.ext_json import json |
|
38 | 38 | from rhodecode.lib.vcs.exceptions import VCSError, NodeNotChangedError |
|
39 | 39 | from rhodecode.model.gist import GistModel |
|
40 | 40 | from rhodecode.model.meta import Session |
|
41 | 41 | from rhodecode.model.db import Gist, User, or_ |
|
42 | 42 | from rhodecode.model import validation_schema |
|
43 | 43 | from rhodecode.model.validation_schema.schemas import gist_schema |
|
44 | 44 | |
|
45 | 45 | |
|
46 | 46 | log = logging.getLogger(__name__) |
|
47 | 47 | |
|
48 | 48 | |
|
49 | 49 | class GistView(BaseAppView): |
|
50 | 50 | |
|
51 | 51 | def load_default_context(self): |
|
52 | 52 | _ = self.request.translate |
|
53 | 53 | c = self._get_local_tmpl_context() |
|
54 | 54 | c.user = c.auth_user.get_instance() |
|
55 | 55 | |
|
56 | 56 | c.lifetime_values = [ |
|
57 | 57 | (-1, _('forever')), |
|
58 | 58 | (5, _('5 minutes')), |
|
59 | 59 | (60, _('1 hour')), |
|
60 | 60 | (60 * 24, _('1 day')), |
|
61 | 61 | (60 * 24 * 30, _('1 month')), |
|
62 | 62 | ] |
|
63 | 63 | |
|
64 | 64 | c.lifetime_options = [(c.lifetime_values, _("Lifetime"))] |
|
65 | 65 | c.acl_options = [ |
|
66 | 66 | (Gist.ACL_LEVEL_PRIVATE, _("Requires registered account")), |
|
67 | 67 | (Gist.ACL_LEVEL_PUBLIC, _("Can be accessed by anonymous users")) |
|
68 | 68 | ] |
|
69 | 69 | |
|
70 | self._register_global_c(c) | |
|
70 | ||
|
71 | 71 | return c |
|
72 | 72 | |
|
73 | 73 | @LoginRequired() |
|
74 | 74 | @view_config( |
|
75 | 75 | route_name='gists_show', request_method='GET', |
|
76 | 76 | renderer='rhodecode:templates/admin/gists/index.mako') |
|
77 | 77 | def gist_show_all(self): |
|
78 | 78 | c = self.load_default_context() |
|
79 | 79 | |
|
80 | 80 | not_default_user = self._rhodecode_user.username != User.DEFAULT_USER |
|
81 | 81 | c.show_private = self.request.GET.get('private') and not_default_user |
|
82 | 82 | c.show_public = self.request.GET.get('public') and not_default_user |
|
83 | 83 | c.show_all = self.request.GET.get('all') and self._rhodecode_user.admin |
|
84 | 84 | |
|
85 | 85 | gists = _gists = Gist().query()\ |
|
86 | 86 | .filter(or_(Gist.gist_expires == -1, Gist.gist_expires >= time.time()))\ |
|
87 | 87 | .order_by(Gist.created_on.desc()) |
|
88 | 88 | |
|
89 | 89 | c.active = 'public' |
|
90 | 90 | # MY private |
|
91 | 91 | if c.show_private and not c.show_public: |
|
92 | 92 | gists = _gists.filter(Gist.gist_type == Gist.GIST_PRIVATE)\ |
|
93 | 93 | .filter(Gist.gist_owner == self._rhodecode_user.user_id) |
|
94 | 94 | c.active = 'my_private' |
|
95 | 95 | # MY public |
|
96 | 96 | elif c.show_public and not c.show_private: |
|
97 | 97 | gists = _gists.filter(Gist.gist_type == Gist.GIST_PUBLIC)\ |
|
98 | 98 | .filter(Gist.gist_owner == self._rhodecode_user.user_id) |
|
99 | 99 | c.active = 'my_public' |
|
100 | 100 | # MY public+private |
|
101 | 101 | elif c.show_private and c.show_public: |
|
102 | 102 | gists = _gists.filter(or_(Gist.gist_type == Gist.GIST_PUBLIC, |
|
103 | 103 | Gist.gist_type == Gist.GIST_PRIVATE))\ |
|
104 | 104 | .filter(Gist.gist_owner == self._rhodecode_user.user_id) |
|
105 | 105 | c.active = 'my_all' |
|
106 | 106 | # Show all by super-admin |
|
107 | 107 | elif c.show_all: |
|
108 | 108 | c.active = 'all' |
|
109 | 109 | gists = _gists |
|
110 | 110 | |
|
111 | 111 | # default show ALL public gists |
|
112 | 112 | if not c.show_public and not c.show_private and not c.show_all: |
|
113 | 113 | gists = _gists.filter(Gist.gist_type == Gist.GIST_PUBLIC) |
|
114 | 114 | c.active = 'public' |
|
115 | 115 | |
|
116 | 116 | _render = self.request.get_partial_renderer( |
|
117 | 117 | 'rhodecode:templates/data_table/_dt_elements.mako') |
|
118 | 118 | |
|
119 | 119 | data = [] |
|
120 | 120 | |
|
121 | 121 | for gist in gists: |
|
122 | 122 | data.append({ |
|
123 | 123 | 'created_on': _render('gist_created', gist.created_on), |
|
124 | 124 | 'created_on_raw': gist.created_on, |
|
125 | 125 | 'type': _render('gist_type', gist.gist_type), |
|
126 | 126 | 'access_id': _render('gist_access_id', gist.gist_access_id, gist.owner.full_contact), |
|
127 | 127 | 'author': _render('gist_author', gist.owner.full_contact, gist.created_on, gist.gist_expires), |
|
128 | 128 | 'author_raw': h.escape(gist.owner.full_contact), |
|
129 | 129 | 'expires': _render('gist_expires', gist.gist_expires), |
|
130 | 130 | 'description': _render('gist_description', gist.gist_description) |
|
131 | 131 | }) |
|
132 | 132 | c.data = json.dumps(data) |
|
133 | 133 | |
|
134 | 134 | return self._get_template_context(c) |
|
135 | 135 | |
|
136 | 136 | @LoginRequired() |
|
137 | 137 | @NotAnonymous() |
|
138 | 138 | @view_config( |
|
139 | 139 | route_name='gists_new', request_method='GET', |
|
140 | 140 | renderer='rhodecode:templates/admin/gists/new.mako') |
|
141 | 141 | def gist_new(self): |
|
142 | 142 | c = self.load_default_context() |
|
143 | 143 | return self._get_template_context(c) |
|
144 | 144 | |
|
145 | 145 | @LoginRequired() |
|
146 | 146 | @NotAnonymous() |
|
147 | 147 | @CSRFRequired() |
|
148 | 148 | @view_config( |
|
149 | 149 | route_name='gists_create', request_method='POST', |
|
150 | 150 | renderer='rhodecode:templates/admin/gists/new.mako') |
|
151 | 151 | def gist_create(self): |
|
152 | 152 | _ = self.request.translate |
|
153 | 153 | c = self.load_default_context() |
|
154 | 154 | |
|
155 | 155 | data = dict(self.request.POST) |
|
156 | 156 | data['filename'] = data.get('filename') or Gist.DEFAULT_FILENAME |
|
157 | 157 | data['nodes'] = [{ |
|
158 | 158 | 'filename': data['filename'], |
|
159 | 159 | 'content': data.get('content'), |
|
160 | 160 | 'mimetype': data.get('mimetype') # None is autodetect |
|
161 | 161 | }] |
|
162 | 162 | |
|
163 | 163 | data['gist_type'] = ( |
|
164 | 164 | Gist.GIST_PUBLIC if data.get('public') else Gist.GIST_PRIVATE) |
|
165 | 165 | data['gist_acl_level'] = ( |
|
166 | 166 | data.get('gist_acl_level') or Gist.ACL_LEVEL_PRIVATE) |
|
167 | 167 | |
|
168 | 168 | schema = gist_schema.GistSchema().bind( |
|
169 | 169 | lifetime_options=[x[0] for x in c.lifetime_values]) |
|
170 | 170 | |
|
171 | 171 | try: |
|
172 | 172 | |
|
173 | 173 | schema_data = schema.deserialize(data) |
|
174 | 174 | # convert to safer format with just KEYs so we sure no duplicates |
|
175 | 175 | schema_data['nodes'] = gist_schema.sequence_to_nodes( |
|
176 | 176 | schema_data['nodes']) |
|
177 | 177 | |
|
178 | 178 | gist = GistModel().create( |
|
179 | 179 | gist_id=schema_data['gistid'], # custom access id not real ID |
|
180 | 180 | description=schema_data['description'], |
|
181 | 181 | owner=self._rhodecode_user.user_id, |
|
182 | 182 | gist_mapping=schema_data['nodes'], |
|
183 | 183 | gist_type=schema_data['gist_type'], |
|
184 | 184 | lifetime=schema_data['lifetime'], |
|
185 | 185 | gist_acl_level=schema_data['gist_acl_level'] |
|
186 | 186 | ) |
|
187 | 187 | Session().commit() |
|
188 | 188 | new_gist_id = gist.gist_access_id |
|
189 | 189 | except validation_schema.Invalid as errors: |
|
190 | 190 | defaults = data |
|
191 | 191 | errors = errors.asdict() |
|
192 | 192 | |
|
193 | 193 | if 'nodes.0.content' in errors: |
|
194 | 194 | errors['content'] = errors['nodes.0.content'] |
|
195 | 195 | del errors['nodes.0.content'] |
|
196 | 196 | if 'nodes.0.filename' in errors: |
|
197 | 197 | errors['filename'] = errors['nodes.0.filename'] |
|
198 | 198 | del errors['nodes.0.filename'] |
|
199 | 199 | |
|
200 | 200 | data = render('rhodecode:templates/admin/gists/new.mako', |
|
201 | 201 | self._get_template_context(c), self.request) |
|
202 | 202 | html = formencode.htmlfill.render( |
|
203 | 203 | data, |
|
204 | 204 | defaults=defaults, |
|
205 | 205 | errors=errors, |
|
206 | 206 | prefix_error=False, |
|
207 | 207 | encoding="UTF-8", |
|
208 | 208 | force_defaults=False |
|
209 | 209 | ) |
|
210 | 210 | return Response(html) |
|
211 | 211 | |
|
212 | 212 | except Exception: |
|
213 | 213 | log.exception("Exception while trying to create a gist") |
|
214 | 214 | h.flash(_('Error occurred during gist creation'), category='error') |
|
215 | 215 | raise HTTPFound(h.route_url('gists_new')) |
|
216 | 216 | raise HTTPFound(h.route_url('gist_show', gist_id=new_gist_id)) |
|
217 | 217 | |
|
218 | 218 | @LoginRequired() |
|
219 | 219 | @NotAnonymous() |
|
220 | 220 | @CSRFRequired() |
|
221 | 221 | @view_config( |
|
222 | 222 | route_name='gist_delete', request_method='POST') |
|
223 | 223 | def gist_delete(self): |
|
224 | 224 | _ = self.request.translate |
|
225 | 225 | gist_id = self.request.matchdict['gist_id'] |
|
226 | 226 | |
|
227 | 227 | c = self.load_default_context() |
|
228 | 228 | c.gist = Gist.get_or_404(gist_id) |
|
229 | 229 | |
|
230 | 230 | owner = c.gist.gist_owner == self._rhodecode_user.user_id |
|
231 | 231 | if not (h.HasPermissionAny('hg.admin')() or owner): |
|
232 | 232 | log.warning('Deletion of Gist was forbidden ' |
|
233 | 233 | 'by unauthorized user: `%s`', self._rhodecode_user) |
|
234 | 234 | raise HTTPNotFound() |
|
235 | 235 | |
|
236 | 236 | GistModel().delete(c.gist) |
|
237 | 237 | Session().commit() |
|
238 | 238 | h.flash(_('Deleted gist %s') % c.gist.gist_access_id, category='success') |
|
239 | 239 | |
|
240 | 240 | raise HTTPFound(h.route_url('gists_show')) |
|
241 | 241 | |
|
242 | 242 | def _get_gist(self, gist_id): |
|
243 | 243 | |
|
244 | 244 | gist = Gist.get_or_404(gist_id) |
|
245 | 245 | |
|
246 | 246 | # Check if this gist is expired |
|
247 | 247 | if gist.gist_expires != -1: |
|
248 | 248 | if time.time() > gist.gist_expires: |
|
249 | 249 | log.error( |
|
250 | 250 | 'Gist expired at %s', time_to_datetime(gist.gist_expires)) |
|
251 | 251 | raise HTTPNotFound() |
|
252 | 252 | |
|
253 | 253 | # check if this gist requires a login |
|
254 | 254 | is_default_user = self._rhodecode_user.username == User.DEFAULT_USER |
|
255 | 255 | if gist.acl_level == Gist.ACL_LEVEL_PRIVATE and is_default_user: |
|
256 | 256 | log.error("Anonymous user %s tried to access protected gist `%s`", |
|
257 | 257 | self._rhodecode_user, gist_id) |
|
258 | 258 | raise HTTPNotFound() |
|
259 | 259 | return gist |
|
260 | 260 | |
|
261 | 261 | @LoginRequired() |
|
262 | 262 | @view_config( |
|
263 | 263 | route_name='gist_show', request_method='GET', |
|
264 | 264 | renderer='rhodecode:templates/admin/gists/show.mako') |
|
265 | 265 | @view_config( |
|
266 | 266 | route_name='gist_show_rev', request_method='GET', |
|
267 | 267 | renderer='rhodecode:templates/admin/gists/show.mako') |
|
268 | 268 | @view_config( |
|
269 | 269 | route_name='gist_show_formatted', request_method='GET', |
|
270 | 270 | renderer=None) |
|
271 | 271 | @view_config( |
|
272 | 272 | route_name='gist_show_formatted_path', request_method='GET', |
|
273 | 273 | renderer=None) |
|
274 | 274 | def gist_show(self): |
|
275 | 275 | gist_id = self.request.matchdict['gist_id'] |
|
276 | 276 | |
|
277 | 277 | # TODO(marcink): expose those via matching dict |
|
278 | 278 | revision = self.request.matchdict.get('revision', 'tip') |
|
279 | 279 | f_path = self.request.matchdict.get('f_path', None) |
|
280 | 280 | return_format = self.request.matchdict.get('format') |
|
281 | 281 | |
|
282 | 282 | c = self.load_default_context() |
|
283 | 283 | c.gist = self._get_gist(gist_id) |
|
284 | 284 | c.render = not self.request.GET.get('no-render', False) |
|
285 | 285 | |
|
286 | 286 | try: |
|
287 | 287 | c.file_last_commit, c.files = GistModel().get_gist_files( |
|
288 | 288 | gist_id, revision=revision) |
|
289 | 289 | except VCSError: |
|
290 | 290 | log.exception("Exception in gist show") |
|
291 | 291 | raise HTTPNotFound() |
|
292 | 292 | |
|
293 | 293 | if return_format == 'raw': |
|
294 | 294 | content = '\n\n'.join([f.content for f in c.files |
|
295 | 295 | if (f_path is None or f.path == f_path)]) |
|
296 | 296 | response = Response(content) |
|
297 | 297 | response.content_type = 'text/plain' |
|
298 | 298 | return response |
|
299 | 299 | |
|
300 | 300 | return self._get_template_context(c) |
|
301 | 301 | |
|
302 | 302 | @LoginRequired() |
|
303 | 303 | @NotAnonymous() |
|
304 | 304 | @view_config( |
|
305 | 305 | route_name='gist_edit', request_method='GET', |
|
306 | 306 | renderer='rhodecode:templates/admin/gists/edit.mako') |
|
307 | 307 | def gist_edit(self): |
|
308 | 308 | _ = self.request.translate |
|
309 | 309 | gist_id = self.request.matchdict['gist_id'] |
|
310 | 310 | c = self.load_default_context() |
|
311 | 311 | c.gist = self._get_gist(gist_id) |
|
312 | 312 | |
|
313 | 313 | owner = c.gist.gist_owner == self._rhodecode_user.user_id |
|
314 | 314 | if not (h.HasPermissionAny('hg.admin')() or owner): |
|
315 | 315 | raise HTTPNotFound() |
|
316 | 316 | |
|
317 | 317 | try: |
|
318 | 318 | c.file_last_commit, c.files = GistModel().get_gist_files(gist_id) |
|
319 | 319 | except VCSError: |
|
320 | 320 | log.exception("Exception in gist edit") |
|
321 | 321 | raise HTTPNotFound() |
|
322 | 322 | |
|
323 | 323 | if c.gist.gist_expires == -1: |
|
324 | 324 | expiry = _('never') |
|
325 | 325 | else: |
|
326 | 326 | # this cannot use timeago, since it's used in select2 as a value |
|
327 | 327 | expiry = h.age(h.time_to_datetime(c.gist.gist_expires)) |
|
328 | 328 | |
|
329 | 329 | c.lifetime_values.append( |
|
330 | 330 | (0, _('%(expiry)s - current value') % {'expiry': _(expiry)}) |
|
331 | 331 | ) |
|
332 | 332 | |
|
333 | 333 | return self._get_template_context(c) |
|
334 | 334 | |
|
335 | 335 | @LoginRequired() |
|
336 | 336 | @NotAnonymous() |
|
337 | 337 | @CSRFRequired() |
|
338 | 338 | @view_config( |
|
339 | 339 | route_name='gist_update', request_method='POST', |
|
340 | 340 | renderer='rhodecode:templates/admin/gists/edit.mako') |
|
341 | 341 | def gist_update(self): |
|
342 | 342 | _ = self.request.translate |
|
343 | 343 | gist_id = self.request.matchdict['gist_id'] |
|
344 | 344 | c = self.load_default_context() |
|
345 | 345 | c.gist = self._get_gist(gist_id) |
|
346 | 346 | |
|
347 | 347 | owner = c.gist.gist_owner == self._rhodecode_user.user_id |
|
348 | 348 | if not (h.HasPermissionAny('hg.admin')() or owner): |
|
349 | 349 | raise HTTPNotFound() |
|
350 | 350 | |
|
351 | 351 | data = peppercorn.parse(self.request.POST.items()) |
|
352 | 352 | |
|
353 | 353 | schema = gist_schema.GistSchema() |
|
354 | 354 | schema = schema.bind( |
|
355 | 355 | # '0' is special value to leave lifetime untouched |
|
356 | 356 | lifetime_options=[x[0] for x in c.lifetime_values] + [0], |
|
357 | 357 | ) |
|
358 | 358 | |
|
359 | 359 | try: |
|
360 | 360 | schema_data = schema.deserialize(data) |
|
361 | 361 | # convert to safer format with just KEYs so we sure no duplicates |
|
362 | 362 | schema_data['nodes'] = gist_schema.sequence_to_nodes( |
|
363 | 363 | schema_data['nodes']) |
|
364 | 364 | |
|
365 | 365 | GistModel().update( |
|
366 | 366 | gist=c.gist, |
|
367 | 367 | description=schema_data['description'], |
|
368 | 368 | owner=c.gist.owner, |
|
369 | 369 | gist_mapping=schema_data['nodes'], |
|
370 | 370 | lifetime=schema_data['lifetime'], |
|
371 | 371 | gist_acl_level=schema_data['gist_acl_level'] |
|
372 | 372 | ) |
|
373 | 373 | |
|
374 | 374 | Session().commit() |
|
375 | 375 | h.flash(_('Successfully updated gist content'), category='success') |
|
376 | 376 | except NodeNotChangedError: |
|
377 | 377 | # raised if nothing was changed in repo itself. We anyway then |
|
378 | 378 | # store only DB stuff for gist |
|
379 | 379 | Session().commit() |
|
380 | 380 | h.flash(_('Successfully updated gist data'), category='success') |
|
381 | 381 | except validation_schema.Invalid as errors: |
|
382 | 382 | errors = h.escape(errors.asdict()) |
|
383 | 383 | h.flash(_('Error occurred during update of gist {}: {}').format( |
|
384 | 384 | gist_id, errors), category='error') |
|
385 | 385 | except Exception: |
|
386 | 386 | log.exception("Exception in gist edit") |
|
387 | 387 | h.flash(_('Error occurred during update of gist %s') % gist_id, |
|
388 | 388 | category='error') |
|
389 | 389 | |
|
390 | 390 | raise HTTPFound(h.route_url('gist_show', gist_id=gist_id)) |
|
391 | 391 | |
|
392 | 392 | @LoginRequired() |
|
393 | 393 | @NotAnonymous() |
|
394 | 394 | @view_config( |
|
395 | 395 | route_name='gist_edit_check_revision', request_method='GET', |
|
396 | 396 | renderer='json_ext') |
|
397 | 397 | def gist_edit_check_revision(self): |
|
398 | 398 | _ = self.request.translate |
|
399 | 399 | gist_id = self.request.matchdict['gist_id'] |
|
400 | 400 | c = self.load_default_context() |
|
401 | 401 | c.gist = self._get_gist(gist_id) |
|
402 | 402 | |
|
403 | 403 | last_rev = c.gist.scm_instance().get_commit() |
|
404 | 404 | success = True |
|
405 | 405 | revision = self.request.GET.get('revision') |
|
406 | 406 | |
|
407 | 407 | if revision != last_rev.raw_id: |
|
408 | 408 | log.error('Last revision %s is different then submitted %s' |
|
409 | 409 | % (revision, last_rev)) |
|
410 | 410 | # our gist has newer version than we |
|
411 | 411 | success = False |
|
412 | 412 | |
|
413 | 413 | return {'success': success} |
@@ -1,151 +1,151 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | |
|
3 | 3 | # Copyright (C) 2016-2017 RhodeCode GmbH |
|
4 | 4 | # |
|
5 | 5 | # This program is free software: you can redistribute it and/or modify |
|
6 | 6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
7 | 7 | # (only), as published by the Free Software Foundation. |
|
8 | 8 | # |
|
9 | 9 | # This program is distributed in the hope that it will be useful, |
|
10 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 | 12 | # GNU General Public License for more details. |
|
13 | 13 | # |
|
14 | 14 | # You should have received a copy of the GNU Affero General Public License |
|
15 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | 16 | # |
|
17 | 17 | # This program is dual-licensed. If you wish to learn more about the |
|
18 | 18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
19 | 19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
20 | 20 | |
|
21 | 21 | import json |
|
22 | 22 | |
|
23 | 23 | import pytest |
|
24 | 24 | |
|
25 | 25 | from . import assert_and_get_content |
|
26 | 26 | from rhodecode.tests import TestController, TEST_USER_ADMIN_LOGIN |
|
27 | 27 | from rhodecode.tests.fixture import Fixture |
|
28 | 28 | |
|
29 | 29 | from rhodecode.lib.utils import map_groups |
|
30 | 30 | from rhodecode.model.repo import RepoModel |
|
31 | 31 | from rhodecode.model.repo_group import RepoGroupModel |
|
32 | 32 | from rhodecode.model.db import Session, Repository, RepoGroup |
|
33 | 33 | |
|
34 | 34 | fixture = Fixture() |
|
35 | 35 | |
|
36 | 36 | |
|
37 | 37 | def route_path(name, params=None, **kwargs): |
|
38 | 38 | import urllib |
|
39 | 39 | |
|
40 | 40 | base_url = { |
|
41 | 41 | 'goto_switcher_data': '/_goto_data', |
|
42 | 42 | }[name].format(**kwargs) |
|
43 | 43 | |
|
44 | 44 | if params: |
|
45 | 45 | base_url = '{}?{}'.format(base_url, urllib.urlencode(params)) |
|
46 | 46 | return base_url |
|
47 | 47 | |
|
48 | 48 | |
|
49 | 49 | class TestGotoSwitcherData(TestController): |
|
50 | 50 | |
|
51 | 51 | required_repos_with_groups = [ |
|
52 | 52 | 'abc', |
|
53 | 53 | 'abc-fork', |
|
54 | 54 | 'forks/abcd', |
|
55 | 55 | 'abcd', |
|
56 | 56 | 'abcde', |
|
57 | 57 | 'a/abc', |
|
58 | 58 | 'aa/abc', |
|
59 | 59 | 'aaa/abc', |
|
60 | 60 | 'aaaa/abc', |
|
61 | 61 | 'repos_abc/aaa/abc', |
|
62 | 62 | 'abc_repos/abc', |
|
63 | 63 | 'abc_repos/abcd', |
|
64 | 64 | 'xxx/xyz', |
|
65 | 65 | 'forked-abc/a/abc' |
|
66 | 66 | ] |
|
67 | 67 | |
|
68 | 68 | @pytest.fixture(autouse=True, scope='class') |
|
69 |
def prepare(self, request, |
|
|
69 | def prepare(self, request, baseapp): | |
|
70 | 70 | for repo_and_group in self.required_repos_with_groups: |
|
71 | 71 | # create structure of groups and return the last group |
|
72 | 72 | |
|
73 | 73 | repo_group = map_groups(repo_and_group) |
|
74 | 74 | |
|
75 | 75 | RepoModel()._create_repo( |
|
76 | 76 | repo_and_group, 'hg', 'test-ac', TEST_USER_ADMIN_LOGIN, |
|
77 | 77 | repo_group=getattr(repo_group, 'group_id', None)) |
|
78 | 78 | |
|
79 | 79 | Session().commit() |
|
80 | 80 | |
|
81 | 81 | request.addfinalizer(self.cleanup) |
|
82 | 82 | |
|
83 | 83 | def cleanup(self): |
|
84 | 84 | # first delete all repos |
|
85 | 85 | for repo_and_groups in self.required_repos_with_groups: |
|
86 | 86 | repo = Repository.get_by_repo_name(repo_and_groups) |
|
87 | 87 | if repo: |
|
88 | 88 | RepoModel().delete(repo) |
|
89 | 89 | Session().commit() |
|
90 | 90 | |
|
91 | 91 | # then delete all empty groups |
|
92 | 92 | for repo_and_groups in self.required_repos_with_groups: |
|
93 | 93 | if '/' in repo_and_groups: |
|
94 | 94 | r_group = repo_and_groups.rsplit('/', 1)[0] |
|
95 | 95 | repo_group = RepoGroup.get_by_group_name(r_group) |
|
96 | 96 | if not repo_group: |
|
97 | 97 | continue |
|
98 | 98 | parents = repo_group.parents |
|
99 | 99 | RepoGroupModel().delete(repo_group, force_delete=True) |
|
100 | 100 | Session().commit() |
|
101 | 101 | |
|
102 | 102 | for el in reversed(parents): |
|
103 | 103 | RepoGroupModel().delete(el, force_delete=True) |
|
104 | 104 | Session().commit() |
|
105 | 105 | |
|
106 | 106 | def test_returns_list_of_repos_and_groups(self, xhr_header): |
|
107 | 107 | self.log_user() |
|
108 | 108 | |
|
109 | 109 | response = self.app.get( |
|
110 | 110 | route_path('goto_switcher_data'), |
|
111 | 111 | extra_environ=xhr_header, status=200) |
|
112 | 112 | result = json.loads(response.body)['results'] |
|
113 | 113 | |
|
114 | 114 | repos, groups, commits = assert_and_get_content(result) |
|
115 | 115 | |
|
116 | 116 | assert len(repos) == len(Repository.get_all()) |
|
117 | 117 | assert len(groups) == len(RepoGroup.get_all()) |
|
118 | 118 | assert len(commits) == 0 |
|
119 | 119 | |
|
120 | 120 | def test_returns_list_of_repos_and_groups_filtered(self, xhr_header): |
|
121 | 121 | self.log_user() |
|
122 | 122 | |
|
123 | 123 | response = self.app.get( |
|
124 | 124 | route_path('goto_switcher_data'), |
|
125 | 125 | params={'query': 'abc'}, |
|
126 | 126 | extra_environ=xhr_header, status=200) |
|
127 | 127 | result = json.loads(response.body)['results'] |
|
128 | 128 | |
|
129 | 129 | repos, groups, commits = assert_and_get_content(result) |
|
130 | 130 | |
|
131 | 131 | assert len(repos) == 13 |
|
132 | 132 | assert len(groups) == 5 |
|
133 | 133 | assert len(commits) == 0 |
|
134 | 134 | |
|
135 | 135 | def test_returns_list_of_properly_sorted_and_filtered(self, xhr_header): |
|
136 | 136 | self.log_user() |
|
137 | 137 | |
|
138 | 138 | response = self.app.get( |
|
139 | 139 | route_path('goto_switcher_data'), |
|
140 | 140 | params={'query': 'abc'}, |
|
141 | 141 | extra_environ=xhr_header, status=200) |
|
142 | 142 | result = json.loads(response.body)['results'] |
|
143 | 143 | |
|
144 | 144 | repos, groups, commits = assert_and_get_content(result) |
|
145 | 145 | |
|
146 | 146 | test_repos = [x['text'] for x in repos[:4]] |
|
147 | 147 | assert ['abc', 'abcd', 'a/abc', 'abcde'] == test_repos |
|
148 | 148 | |
|
149 | 149 | test_groups = [x['text'] for x in groups[:4]] |
|
150 | 150 | assert ['abc_repos', 'repos_abc', |
|
151 | 151 | 'forked-abc', 'forked-abc/a'] == test_groups |
@@ -1,325 +1,325 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | |
|
3 | 3 | # Copyright (C) 2016-2017 RhodeCode GmbH |
|
4 | 4 | # |
|
5 | 5 | # This program is free software: you can redistribute it and/or modify |
|
6 | 6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
7 | 7 | # (only), as published by the Free Software Foundation. |
|
8 | 8 | # |
|
9 | 9 | # This program is distributed in the hope that it will be useful, |
|
10 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 | 12 | # GNU General Public License for more details. |
|
13 | 13 | # |
|
14 | 14 | # You should have received a copy of the GNU Affero General Public License |
|
15 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | 16 | # |
|
17 | 17 | # This program is dual-licensed. If you wish to learn more about the |
|
18 | 18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
19 | 19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
20 | 20 | |
|
21 | 21 | import re |
|
22 | 22 | import logging |
|
23 | 23 | |
|
24 | 24 | from pyramid.view import view_config |
|
25 | 25 | |
|
26 | 26 | from rhodecode.apps._base import BaseAppView |
|
27 | 27 | from rhodecode.lib import helpers as h |
|
28 | 28 | from rhodecode.lib.auth import ( |
|
29 | 29 | LoginRequired, NotAnonymous, HasRepoGroupPermissionAnyDecorator) |
|
30 | 30 | from rhodecode.lib.index import searcher_from_config |
|
31 | 31 | from rhodecode.lib.utils2 import safe_unicode, str2bool |
|
32 | 32 | from rhodecode.lib.ext_json import json |
|
33 | 33 | from rhodecode.model.db import ( |
|
34 | 34 | func, or_, in_filter_generator, Repository, RepoGroup) |
|
35 | 35 | from rhodecode.model.repo import RepoModel |
|
36 | 36 | from rhodecode.model.repo_group import RepoGroupModel |
|
37 | 37 | from rhodecode.model.scm import RepoGroupList, RepoList |
|
38 | 38 | from rhodecode.model.user import UserModel |
|
39 | 39 | from rhodecode.model.user_group import UserGroupModel |
|
40 | 40 | |
|
41 | 41 | log = logging.getLogger(__name__) |
|
42 | 42 | |
|
43 | 43 | |
|
44 | 44 | class HomeView(BaseAppView): |
|
45 | 45 | |
|
46 | 46 | def load_default_context(self): |
|
47 | 47 | c = self._get_local_tmpl_context() |
|
48 | 48 | c.user = c.auth_user.get_instance() |
|
49 | self._register_global_c(c) | |
|
49 | ||
|
50 | 50 | return c |
|
51 | 51 | |
|
52 | 52 | @LoginRequired() |
|
53 | 53 | @view_config( |
|
54 | 54 | route_name='user_autocomplete_data', request_method='GET', |
|
55 | 55 | renderer='json_ext', xhr=True) |
|
56 | 56 | def user_autocomplete_data(self): |
|
57 | 57 | self.load_default_context() |
|
58 | 58 | query = self.request.GET.get('query') |
|
59 | 59 | active = str2bool(self.request.GET.get('active') or True) |
|
60 | 60 | include_groups = str2bool(self.request.GET.get('user_groups')) |
|
61 | 61 | expand_groups = str2bool(self.request.GET.get('user_groups_expand')) |
|
62 | 62 | skip_default_user = str2bool(self.request.GET.get('skip_default_user')) |
|
63 | 63 | |
|
64 | 64 | log.debug('generating user list, query:%s, active:%s, with_groups:%s', |
|
65 | 65 | query, active, include_groups) |
|
66 | 66 | |
|
67 | 67 | _users = UserModel().get_users( |
|
68 | 68 | name_contains=query, only_active=active) |
|
69 | 69 | |
|
70 | 70 | def maybe_skip_default_user(usr): |
|
71 | 71 | if skip_default_user and usr['username'] == UserModel.cls.DEFAULT_USER: |
|
72 | 72 | return False |
|
73 | 73 | return True |
|
74 | 74 | _users = filter(maybe_skip_default_user, _users) |
|
75 | 75 | |
|
76 | 76 | if include_groups: |
|
77 | 77 | # extend with user groups |
|
78 | 78 | _user_groups = UserGroupModel().get_user_groups( |
|
79 | 79 | name_contains=query, only_active=active, |
|
80 | 80 | expand_groups=expand_groups) |
|
81 | 81 | _users = _users + _user_groups |
|
82 | 82 | |
|
83 | 83 | return {'suggestions': _users} |
|
84 | 84 | |
|
85 | 85 | @LoginRequired() |
|
86 | 86 | @NotAnonymous() |
|
87 | 87 | @view_config( |
|
88 | 88 | route_name='user_group_autocomplete_data', request_method='GET', |
|
89 | 89 | renderer='json_ext', xhr=True) |
|
90 | 90 | def user_group_autocomplete_data(self): |
|
91 | 91 | self.load_default_context() |
|
92 | 92 | query = self.request.GET.get('query') |
|
93 | 93 | active = str2bool(self.request.GET.get('active') or True) |
|
94 | 94 | expand_groups = str2bool(self.request.GET.get('user_groups_expand')) |
|
95 | 95 | |
|
96 | 96 | log.debug('generating user group list, query:%s, active:%s', |
|
97 | 97 | query, active) |
|
98 | 98 | |
|
99 | 99 | _user_groups = UserGroupModel().get_user_groups( |
|
100 | 100 | name_contains=query, only_active=active, |
|
101 | 101 | expand_groups=expand_groups) |
|
102 | 102 | _user_groups = _user_groups |
|
103 | 103 | |
|
104 | 104 | return {'suggestions': _user_groups} |
|
105 | 105 | |
|
106 | 106 | def _get_repo_list(self, name_contains=None, repo_type=None, limit=20): |
|
107 | 107 | allowed_ids = self._rhodecode_user.repo_acl_ids( |
|
108 | 108 | ['repository.read', 'repository.write', 'repository.admin'], |
|
109 | 109 | cache=False, name_filter=name_contains) or [-1] |
|
110 | 110 | |
|
111 | 111 | query = Repository.query()\ |
|
112 | 112 | .order_by(func.length(Repository.repo_name))\ |
|
113 | 113 | .order_by(Repository.repo_name)\ |
|
114 | 114 | .filter(or_( |
|
115 | 115 | # generate multiple IN to fix limitation problems |
|
116 | 116 | *in_filter_generator(Repository.repo_id, allowed_ids) |
|
117 | 117 | )) |
|
118 | 118 | |
|
119 | 119 | if repo_type: |
|
120 | 120 | query = query.filter(Repository.repo_type == repo_type) |
|
121 | 121 | |
|
122 | 122 | if name_contains: |
|
123 | 123 | ilike_expression = u'%{}%'.format(safe_unicode(name_contains)) |
|
124 | 124 | query = query.filter( |
|
125 | 125 | Repository.repo_name.ilike(ilike_expression)) |
|
126 | 126 | query = query.limit(limit) |
|
127 | 127 | |
|
128 | 128 | acl_repo_iter = query |
|
129 | 129 | |
|
130 | 130 | return [ |
|
131 | 131 | { |
|
132 | 132 | 'id': obj.repo_name, |
|
133 | 133 | 'text': obj.repo_name, |
|
134 | 134 | 'type': 'repo', |
|
135 | 135 | 'obj': {'repo_type': obj.repo_type, 'private': obj.private, |
|
136 | 136 | 'repo_id': obj.repo_id}, |
|
137 | 137 | 'url': h.route_path('repo_summary', repo_name=obj.repo_name) |
|
138 | 138 | } |
|
139 | 139 | for obj in acl_repo_iter] |
|
140 | 140 | |
|
141 | 141 | def _get_repo_group_list(self, name_contains=None, limit=20): |
|
142 | 142 | allowed_ids = self._rhodecode_user.repo_group_acl_ids( |
|
143 | 143 | ['group.read', 'group.write', 'group.admin'], |
|
144 | 144 | cache=False, name_filter=name_contains) or [-1] |
|
145 | 145 | |
|
146 | 146 | query = RepoGroup.query()\ |
|
147 | 147 | .order_by(func.length(RepoGroup.group_name))\ |
|
148 | 148 | .order_by(RepoGroup.group_name) \ |
|
149 | 149 | .filter(or_( |
|
150 | 150 | # generate multiple IN to fix limitation problems |
|
151 | 151 | *in_filter_generator(RepoGroup.group_id, allowed_ids) |
|
152 | 152 | )) |
|
153 | 153 | |
|
154 | 154 | if name_contains: |
|
155 | 155 | ilike_expression = u'%{}%'.format(safe_unicode(name_contains)) |
|
156 | 156 | query = query.filter( |
|
157 | 157 | RepoGroup.group_name.ilike(ilike_expression)) |
|
158 | 158 | query = query.limit(limit) |
|
159 | 159 | |
|
160 | 160 | acl_repo_iter = query |
|
161 | 161 | |
|
162 | 162 | return [ |
|
163 | 163 | { |
|
164 | 164 | 'id': obj.group_name, |
|
165 | 165 | 'text': obj.group_name, |
|
166 | 166 | 'type': 'group', |
|
167 | 167 | 'obj': {}, |
|
168 | 168 | 'url': h.route_path( |
|
169 | 169 | 'repo_group_home', repo_group_name=obj.group_name) |
|
170 | 170 | } |
|
171 | 171 | for obj in acl_repo_iter] |
|
172 | 172 | |
|
173 | 173 | def _get_hash_commit_list(self, auth_user, query=None): |
|
174 | 174 | if not query or len(query) < 3: |
|
175 | 175 | return [] |
|
176 | 176 | |
|
177 | 177 | commit_hashes = re.compile('(?:commit:)([0-9a-f]{2,40})').findall(query) |
|
178 | 178 | |
|
179 | 179 | if len(commit_hashes) != 1: |
|
180 | 180 | return [] |
|
181 | 181 | |
|
182 | 182 | commit_hash_prefix = commit_hashes[0] |
|
183 | 183 | |
|
184 | 184 | searcher = searcher_from_config(self.request.registry.settings) |
|
185 | 185 | result = searcher.search( |
|
186 | 186 | 'commit_id:%s*' % commit_hash_prefix, 'commit', auth_user, |
|
187 | 187 | raise_on_exc=False) |
|
188 | 188 | |
|
189 | 189 | return [ |
|
190 | 190 | { |
|
191 | 191 | 'id': entry['commit_id'], |
|
192 | 192 | 'text': entry['commit_id'], |
|
193 | 193 | 'type': 'commit', |
|
194 | 194 | 'obj': {'repo': entry['repository']}, |
|
195 | 195 | 'url': h.route_path( |
|
196 | 196 | 'repo_commit', |
|
197 | 197 | repo_name=entry['repository'], commit_id=entry['commit_id']) |
|
198 | 198 | } |
|
199 | 199 | for entry in result['results']] |
|
200 | 200 | |
|
201 | 201 | @LoginRequired() |
|
202 | 202 | @view_config( |
|
203 | 203 | route_name='repo_list_data', request_method='GET', |
|
204 | 204 | renderer='json_ext', xhr=True) |
|
205 | 205 | def repo_list_data(self): |
|
206 | 206 | _ = self.request.translate |
|
207 | 207 | self.load_default_context() |
|
208 | 208 | |
|
209 | 209 | query = self.request.GET.get('query') |
|
210 | 210 | repo_type = self.request.GET.get('repo_type') |
|
211 | 211 | log.debug('generating repo list, query:%s, repo_type:%s', |
|
212 | 212 | query, repo_type) |
|
213 | 213 | |
|
214 | 214 | res = [] |
|
215 | 215 | repos = self._get_repo_list(query, repo_type=repo_type) |
|
216 | 216 | if repos: |
|
217 | 217 | res.append({ |
|
218 | 218 | 'text': _('Repositories'), |
|
219 | 219 | 'children': repos |
|
220 | 220 | }) |
|
221 | 221 | |
|
222 | 222 | data = { |
|
223 | 223 | 'more': False, |
|
224 | 224 | 'results': res |
|
225 | 225 | } |
|
226 | 226 | return data |
|
227 | 227 | |
|
228 | 228 | @LoginRequired() |
|
229 | 229 | @view_config( |
|
230 | 230 | route_name='goto_switcher_data', request_method='GET', |
|
231 | 231 | renderer='json_ext', xhr=True) |
|
232 | 232 | def goto_switcher_data(self): |
|
233 | 233 | c = self.load_default_context() |
|
234 | 234 | |
|
235 | 235 | _ = self.request.translate |
|
236 | 236 | |
|
237 | 237 | query = self.request.GET.get('query') |
|
238 | 238 | log.debug('generating goto switcher list, query %s', query) |
|
239 | 239 | |
|
240 | 240 | res = [] |
|
241 | 241 | repo_groups = self._get_repo_group_list(query) |
|
242 | 242 | if repo_groups: |
|
243 | 243 | res.append({ |
|
244 | 244 | 'text': _('Groups'), |
|
245 | 245 | 'children': repo_groups |
|
246 | 246 | }) |
|
247 | 247 | |
|
248 | 248 | repos = self._get_repo_list(query) |
|
249 | 249 | if repos: |
|
250 | 250 | res.append({ |
|
251 | 251 | 'text': _('Repositories'), |
|
252 | 252 | 'children': repos |
|
253 | 253 | }) |
|
254 | 254 | |
|
255 | 255 | commits = self._get_hash_commit_list(c.auth_user, query) |
|
256 | 256 | if commits: |
|
257 | 257 | unique_repos = {} |
|
258 | 258 | for commit in commits: |
|
259 | 259 | unique_repos.setdefault(commit['obj']['repo'], [] |
|
260 | 260 | ).append(commit) |
|
261 | 261 | |
|
262 | 262 | for repo in unique_repos: |
|
263 | 263 | res.append({ |
|
264 | 264 | 'text': _('Commits in %(repo)s') % {'repo': repo}, |
|
265 | 265 | 'children': unique_repos[repo] |
|
266 | 266 | }) |
|
267 | 267 | |
|
268 | 268 | data = { |
|
269 | 269 | 'more': False, |
|
270 | 270 | 'results': res |
|
271 | 271 | } |
|
272 | 272 | return data |
|
273 | 273 | |
|
274 | 274 | def _get_groups_and_repos(self, repo_group_id=None): |
|
275 | 275 | # repo groups groups |
|
276 | 276 | repo_group_list = RepoGroup.get_all_repo_groups(group_id=repo_group_id) |
|
277 | 277 | _perms = ['group.read', 'group.write', 'group.admin'] |
|
278 | 278 | repo_group_list_acl = RepoGroupList(repo_group_list, perm_set=_perms) |
|
279 | 279 | repo_group_data = RepoGroupModel().get_repo_groups_as_dict( |
|
280 | 280 | repo_group_list=repo_group_list_acl, admin=False) |
|
281 | 281 | |
|
282 | 282 | # repositories |
|
283 | 283 | repo_list = Repository.get_all_repos(group_id=repo_group_id) |
|
284 | 284 | _perms = ['repository.read', 'repository.write', 'repository.admin'] |
|
285 | 285 | repo_list_acl = RepoList(repo_list, perm_set=_perms) |
|
286 | 286 | repo_data = RepoModel().get_repos_as_dict( |
|
287 | 287 | repo_list=repo_list_acl, admin=False) |
|
288 | 288 | |
|
289 | 289 | return repo_data, repo_group_data |
|
290 | 290 | |
|
291 | 291 | @LoginRequired() |
|
292 | 292 | @view_config( |
|
293 | 293 | route_name='home', request_method='GET', |
|
294 | 294 | renderer='rhodecode:templates/index.mako') |
|
295 | 295 | def main_page(self): |
|
296 | 296 | c = self.load_default_context() |
|
297 | 297 | c.repo_group = None |
|
298 | 298 | |
|
299 | 299 | repo_data, repo_group_data = self._get_groups_and_repos() |
|
300 | 300 | # json used to render the grids |
|
301 | 301 | c.repos_data = json.dumps(repo_data) |
|
302 | 302 | c.repo_groups_data = json.dumps(repo_group_data) |
|
303 | 303 | |
|
304 | 304 | return self._get_template_context(c) |
|
305 | 305 | |
|
306 | 306 | @LoginRequired() |
|
307 | 307 | @HasRepoGroupPermissionAnyDecorator( |
|
308 | 308 | 'group.read', 'group.write', 'group.admin') |
|
309 | 309 | @view_config( |
|
310 | 310 | route_name='repo_group_home', request_method='GET', |
|
311 | 311 | renderer='rhodecode:templates/index_repo_group.mako') |
|
312 | 312 | @view_config( |
|
313 | 313 | route_name='repo_group_home_slash', request_method='GET', |
|
314 | 314 | renderer='rhodecode:templates/index_repo_group.mako') |
|
315 | 315 | def repo_group_main_page(self): |
|
316 | 316 | c = self.load_default_context() |
|
317 | 317 | c.repo_group = self.request.db_repo_group |
|
318 | 318 | repo_data, repo_group_data = self._get_groups_and_repos( |
|
319 | 319 | c.repo_group.group_id) |
|
320 | 320 | |
|
321 | 321 | # json used to render the grids |
|
322 | 322 | c.repos_data = json.dumps(repo_data) |
|
323 | 323 | c.repo_groups_data = json.dumps(repo_group_data) |
|
324 | 324 | |
|
325 | 325 | return self._get_template_context(c) |
@@ -1,384 +1,386 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | |
|
3 | 3 | # Copyright (C) 2010-2017 RhodeCode GmbH |
|
4 | 4 | # |
|
5 | 5 | # This program is free software: you can redistribute it and/or modify |
|
6 | 6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
7 | 7 | # (only), as published by the Free Software Foundation. |
|
8 | 8 | # |
|
9 | 9 | # This program is distributed in the hope that it will be useful, |
|
10 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 | 12 | # GNU General Public License for more details. |
|
13 | 13 | # |
|
14 | 14 | # You should have received a copy of the GNU Affero General Public License |
|
15 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | 16 | # |
|
17 | 17 | # This program is dual-licensed. If you wish to learn more about the |
|
18 | 18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
19 | 19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
20 | 20 | |
|
21 | 21 | |
|
22 | 22 | import logging |
|
23 | 23 | import itertools |
|
24 | 24 | |
|
25 | 25 | from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed |
|
26 | 26 | |
|
27 | 27 | from pyramid.view import view_config |
|
28 | 28 | from pyramid.httpexceptions import HTTPBadRequest |
|
29 | 29 | from pyramid.response import Response |
|
30 | 30 | from pyramid.renderers import render |
|
31 | 31 | |
|
32 | 32 | from rhodecode.apps._base import BaseAppView |
|
33 | 33 | from rhodecode.model.db import ( |
|
34 | 34 | or_, joinedload, UserLog, UserFollowing, User, UserApiKeys) |
|
35 | 35 | from rhodecode.model.meta import Session |
|
36 | 36 | import rhodecode.lib.helpers as h |
|
37 | 37 | from rhodecode.lib.helpers import Page |
|
38 | 38 | from rhodecode.lib.user_log_filter import user_log_filter |
|
39 | 39 | from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired |
|
40 | 40 | from rhodecode.lib.utils2 import safe_int, AttributeDict, md5_safe |
|
41 | 41 | from rhodecode.model.scm import ScmModel |
|
42 | 42 | |
|
43 | 43 | log = logging.getLogger(__name__) |
|
44 | 44 | |
|
45 | 45 | |
|
46 | 46 | class JournalView(BaseAppView): |
|
47 | 47 | |
|
48 | 48 | def load_default_context(self): |
|
49 | 49 | c = self._get_local_tmpl_context(include_app_defaults=True) |
|
50 | self._register_global_c(c) | |
|
50 | ||
|
51 | 51 | self._load_defaults(c.rhodecode_name) |
|
52 | 52 | |
|
53 | 53 | # TODO(marcink): what is this, why we need a global register ? |
|
54 | 54 | c.search_term = self.request.GET.get('filter') or '' |
|
55 | 55 | return c |
|
56 | 56 | |
|
57 | 57 | def _get_config(self, rhodecode_name): |
|
58 | 58 | import rhodecode |
|
59 | 59 | config = rhodecode.CONFIG |
|
60 | 60 | |
|
61 | 61 | return { |
|
62 | 62 | 'language': 'en-us', |
|
63 | 63 | 'feed_ttl': '5', # TTL of feed, |
|
64 | 64 | 'feed_items_per_page': |
|
65 | 65 | safe_int(config.get('rss_items_per_page', 20)), |
|
66 | 66 | 'rhodecode_name': rhodecode_name |
|
67 | 67 | } |
|
68 | 68 | |
|
69 | 69 | def _load_defaults(self, rhodecode_name): |
|
70 | 70 | config = self._get_config(rhodecode_name) |
|
71 | 71 | # common values for feeds |
|
72 | 72 | self.language = config["language"] |
|
73 | 73 | self.ttl = config["feed_ttl"] |
|
74 | 74 | self.feed_items_per_page = config['feed_items_per_page'] |
|
75 | 75 | self.rhodecode_name = config['rhodecode_name'] |
|
76 | 76 | |
|
77 | 77 | def _get_daily_aggregate(self, journal): |
|
78 | 78 | groups = [] |
|
79 | 79 | for k, g in itertools.groupby(journal, lambda x: x.action_as_day): |
|
80 | 80 | user_group = [] |
|
81 | 81 | # groupby username if it's a present value, else |
|
82 | 82 | # fallback to journal username |
|
83 | 83 | for _, g2 in itertools.groupby( |
|
84 | 84 | list(g), lambda x: x.user.username if x.user else x.username): |
|
85 | 85 | l = list(g2) |
|
86 | 86 | user_group.append((l[0].user, l)) |
|
87 | 87 | |
|
88 | 88 | groups.append((k, user_group,)) |
|
89 | 89 | |
|
90 | 90 | return groups |
|
91 | 91 | |
|
92 | 92 | def _get_journal_data(self, following_repos, search_term): |
|
93 | 93 | repo_ids = [x.follows_repository.repo_id for x in following_repos |
|
94 | 94 | if x.follows_repository is not None] |
|
95 | 95 | user_ids = [x.follows_user.user_id for x in following_repos |
|
96 | 96 | if x.follows_user is not None] |
|
97 | 97 | |
|
98 | 98 | filtering_criterion = None |
|
99 | 99 | |
|
100 | 100 | if repo_ids and user_ids: |
|
101 | 101 | filtering_criterion = or_(UserLog.repository_id.in_(repo_ids), |
|
102 | 102 | UserLog.user_id.in_(user_ids)) |
|
103 | 103 | if repo_ids and not user_ids: |
|
104 | 104 | filtering_criterion = UserLog.repository_id.in_(repo_ids) |
|
105 | 105 | if not repo_ids and user_ids: |
|
106 | 106 | filtering_criterion = UserLog.user_id.in_(user_ids) |
|
107 | 107 | if filtering_criterion is not None: |
|
108 | 108 | journal = Session().query(UserLog)\ |
|
109 | 109 | .options(joinedload(UserLog.user))\ |
|
110 | 110 | .options(joinedload(UserLog.repository)) |
|
111 | 111 | # filter |
|
112 | 112 | try: |
|
113 | 113 | journal = user_log_filter(journal, search_term) |
|
114 | 114 | except Exception: |
|
115 | 115 | # we want this to crash for now |
|
116 | 116 | raise |
|
117 | 117 | journal = journal.filter(filtering_criterion)\ |
|
118 | 118 | .order_by(UserLog.action_date.desc()) |
|
119 | 119 | else: |
|
120 | 120 | journal = [] |
|
121 | 121 | |
|
122 | 122 | return journal |
|
123 | 123 | |
|
124 | 124 | def feed_uid(self, entry_id): |
|
125 | 125 | return '{}:{}'.format('journal', md5_safe(entry_id)) |
|
126 | 126 | |
|
127 | 127 | def _atom_feed(self, repos, search_term, public=True): |
|
128 | 128 | _ = self.request.translate |
|
129 | 129 | journal = self._get_journal_data(repos, search_term) |
|
130 | 130 | if public: |
|
131 | 131 | _link = h.route_url('journal_public_atom') |
|
132 | 132 | _desc = '%s %s %s' % (self.rhodecode_name, _('public journal'), |
|
133 | 133 | 'atom feed') |
|
134 | 134 | else: |
|
135 | 135 | _link = h.route_url('journal_atom') |
|
136 | 136 | _desc = '%s %s %s' % (self.rhodecode_name, _('journal'), 'atom feed') |
|
137 | 137 | |
|
138 | 138 | feed = Atom1Feed( |
|
139 | 139 | title=_desc, link=_link, description=_desc, |
|
140 | 140 | language=self.language, ttl=self.ttl) |
|
141 | 141 | |
|
142 | 142 | for entry in journal[:self.feed_items_per_page]: |
|
143 | 143 | user = entry.user |
|
144 | 144 | if user is None: |
|
145 | 145 | # fix deleted users |
|
146 | 146 | user = AttributeDict({'short_contact': entry.username, |
|
147 | 147 | 'email': '', |
|
148 | 148 | 'full_contact': ''}) |
|
149 |
action, action_extra, ico = h.action_parser( |
|
|
149 | action, action_extra, ico = h.action_parser( | |
|
150 | self.request, entry, feed=True) | |
|
150 | 151 | title = "%s - %s %s" % (user.short_contact, action(), |
|
151 | 152 | entry.repository.repo_name) |
|
152 | 153 | desc = action_extra() |
|
153 | 154 | _url = h.route_url('home') |
|
154 | 155 | if entry.repository is not None: |
|
155 | 156 | _url = h.route_url('repo_changelog', |
|
156 | 157 | repo_name=entry.repository.repo_name) |
|
157 | 158 | |
|
158 | 159 | feed.add_item( |
|
159 | 160 | unique_id=self.feed_uid(entry.user_log_id), |
|
160 | 161 | title=title, |
|
161 | 162 | pubdate=entry.action_date, |
|
162 | 163 | link=_url, |
|
163 | 164 | author_email=user.email, |
|
164 | 165 | author_name=user.full_contact, |
|
165 | 166 | description=desc) |
|
166 | 167 | |
|
167 | 168 | response = Response(feed.writeString('utf-8')) |
|
168 | 169 | response.content_type = feed.mime_type |
|
169 | 170 | return response |
|
170 | 171 | |
|
171 | 172 | def _rss_feed(self, repos, search_term, public=True): |
|
172 | 173 | _ = self.request.translate |
|
173 | 174 | journal = self._get_journal_data(repos, search_term) |
|
174 | 175 | if public: |
|
175 | 176 | _link = h.route_url('journal_public_atom') |
|
176 | 177 | _desc = '%s %s %s' % ( |
|
177 | 178 | self.rhodecode_name, _('public journal'), 'rss feed') |
|
178 | 179 | else: |
|
179 | 180 | _link = h.route_url('journal_atom') |
|
180 | 181 | _desc = '%s %s %s' % ( |
|
181 | 182 | self.rhodecode_name, _('journal'), 'rss feed') |
|
182 | 183 | |
|
183 | 184 | feed = Rss201rev2Feed( |
|
184 | 185 | title=_desc, link=_link, description=_desc, |
|
185 | 186 | language=self.language, ttl=self.ttl) |
|
186 | 187 | |
|
187 | 188 | for entry in journal[:self.feed_items_per_page]: |
|
188 | 189 | user = entry.user |
|
189 | 190 | if user is None: |
|
190 | 191 | # fix deleted users |
|
191 | 192 | user = AttributeDict({'short_contact': entry.username, |
|
192 | 193 | 'email': '', |
|
193 | 194 | 'full_contact': ''}) |
|
194 |
action, action_extra, ico = h.action_parser( |
|
|
195 | action, action_extra, ico = h.action_parser( | |
|
196 | self.request, entry, feed=True) | |
|
195 | 197 | title = "%s - %s %s" % (user.short_contact, action(), |
|
196 | 198 | entry.repository.repo_name) |
|
197 | 199 | desc = action_extra() |
|
198 | 200 | _url = h.route_url('home') |
|
199 | 201 | if entry.repository is not None: |
|
200 | 202 | _url = h.route_url('repo_changelog', |
|
201 | 203 | repo_name=entry.repository.repo_name) |
|
202 | 204 | |
|
203 | 205 | feed.add_item( |
|
204 | 206 | unique_id=self.feed_uid(entry.user_log_id), |
|
205 | 207 | title=title, |
|
206 | 208 | pubdate=entry.action_date, |
|
207 | 209 | link=_url, |
|
208 | 210 | author_email=user.email, |
|
209 | 211 | author_name=user.full_contact, |
|
210 | 212 | description=desc) |
|
211 | 213 | |
|
212 | 214 | response = Response(feed.writeString('utf-8')) |
|
213 | 215 | response.content_type = feed.mime_type |
|
214 | 216 | return response |
|
215 | 217 | |
|
216 | 218 | @LoginRequired() |
|
217 | 219 | @NotAnonymous() |
|
218 | 220 | @view_config( |
|
219 | 221 | route_name='journal', request_method='GET', |
|
220 | 222 | renderer=None) |
|
221 | 223 | def journal(self): |
|
222 | 224 | c = self.load_default_context() |
|
223 | 225 | |
|
224 | 226 | p = safe_int(self.request.GET.get('page', 1), 1) |
|
225 | 227 | c.user = User.get(self._rhodecode_user.user_id) |
|
226 | 228 | following = Session().query(UserFollowing)\ |
|
227 | 229 | .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\ |
|
228 | 230 | .options(joinedload(UserFollowing.follows_repository))\ |
|
229 | 231 | .all() |
|
230 | 232 | |
|
231 | 233 | journal = self._get_journal_data(following, c.search_term) |
|
232 | 234 | |
|
233 | 235 | def url_generator(**kw): |
|
234 | 236 | query_params = { |
|
235 | 237 | 'filter': c.search_term |
|
236 | 238 | } |
|
237 | 239 | query_params.update(kw) |
|
238 | 240 | return self.request.current_route_path(_query=query_params) |
|
239 | 241 | |
|
240 | 242 | c.journal_pager = Page( |
|
241 | 243 | journal, page=p, items_per_page=20, url=url_generator) |
|
242 | 244 | c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager) |
|
243 | 245 | |
|
244 | 246 | c.journal_data = render( |
|
245 | 247 | 'rhodecode:templates/journal/journal_data.mako', |
|
246 | 248 | self._get_template_context(c), self.request) |
|
247 | 249 | |
|
248 | 250 | if self.request.is_xhr: |
|
249 | 251 | return Response(c.journal_data) |
|
250 | 252 | |
|
251 | 253 | html = render( |
|
252 | 254 | 'rhodecode:templates/journal/journal.mako', |
|
253 | 255 | self._get_template_context(c), self.request) |
|
254 | 256 | return Response(html) |
|
255 | 257 | |
|
256 | 258 | @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED]) |
|
257 | 259 | @NotAnonymous() |
|
258 | 260 | @view_config( |
|
259 | 261 | route_name='journal_atom', request_method='GET', |
|
260 | 262 | renderer=None) |
|
261 | 263 | def journal_atom(self): |
|
262 | 264 | """ |
|
263 | 265 | Produce an atom-1.0 feed via feedgenerator module |
|
264 | 266 | """ |
|
265 | 267 | c = self.load_default_context() |
|
266 | 268 | following_repos = Session().query(UserFollowing)\ |
|
267 | 269 | .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\ |
|
268 | 270 | .options(joinedload(UserFollowing.follows_repository))\ |
|
269 | 271 | .all() |
|
270 | 272 | return self._atom_feed(following_repos, c.search_term, public=False) |
|
271 | 273 | |
|
272 | 274 | @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED]) |
|
273 | 275 | @NotAnonymous() |
|
274 | 276 | @view_config( |
|
275 | 277 | route_name='journal_rss', request_method='GET', |
|
276 | 278 | renderer=None) |
|
277 | 279 | def journal_rss(self): |
|
278 | 280 | """ |
|
279 | 281 | Produce an rss feed via feedgenerator module |
|
280 | 282 | """ |
|
281 | 283 | c = self.load_default_context() |
|
282 | 284 | following_repos = Session().query(UserFollowing)\ |
|
283 | 285 | .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\ |
|
284 | 286 | .options(joinedload(UserFollowing.follows_repository))\ |
|
285 | 287 | .all() |
|
286 | 288 | return self._rss_feed(following_repos, c.search_term, public=False) |
|
287 | 289 | |
|
288 | 290 | @LoginRequired() |
|
289 | 291 | @NotAnonymous() |
|
290 | 292 | @CSRFRequired() |
|
291 | 293 | @view_config( |
|
292 | 294 | route_name='toggle_following', request_method='POST', |
|
293 | 295 | renderer='json_ext') |
|
294 | 296 | def toggle_following(self): |
|
295 | 297 | user_id = self.request.POST.get('follows_user_id') |
|
296 | 298 | if user_id: |
|
297 | 299 | try: |
|
298 | 300 | ScmModel().toggle_following_user( |
|
299 | 301 | user_id, self._rhodecode_user.user_id) |
|
300 | 302 | Session().commit() |
|
301 | 303 | return 'ok' |
|
302 | 304 | except Exception: |
|
303 | 305 | raise HTTPBadRequest() |
|
304 | 306 | |
|
305 | 307 | repo_id = self.request.POST.get('follows_repo_id') |
|
306 | 308 | if repo_id: |
|
307 | 309 | try: |
|
308 | 310 | ScmModel().toggle_following_repo( |
|
309 | 311 | repo_id, self._rhodecode_user.user_id) |
|
310 | 312 | Session().commit() |
|
311 | 313 | return 'ok' |
|
312 | 314 | except Exception: |
|
313 | 315 | raise HTTPBadRequest() |
|
314 | 316 | |
|
315 | 317 | raise HTTPBadRequest() |
|
316 | 318 | |
|
317 | 319 | @LoginRequired() |
|
318 | 320 | @view_config( |
|
319 | 321 | route_name='journal_public', request_method='GET', |
|
320 | 322 | renderer=None) |
|
321 | 323 | def journal_public(self): |
|
322 | 324 | c = self.load_default_context() |
|
323 | 325 | # Return a rendered template |
|
324 | 326 | p = safe_int(self.request.GET.get('page', 1), 1) |
|
325 | 327 | |
|
326 | 328 | c.following = Session().query(UserFollowing)\ |
|
327 | 329 | .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\ |
|
328 | 330 | .options(joinedload(UserFollowing.follows_repository))\ |
|
329 | 331 | .all() |
|
330 | 332 | |
|
331 | 333 | journal = self._get_journal_data(c.following, c.search_term) |
|
332 | 334 | |
|
333 | 335 | def url_generator(**kw): |
|
334 | 336 | query_params = {} |
|
335 | 337 | query_params.update(kw) |
|
336 | 338 | return self.request.current_route_path(_query=query_params) |
|
337 | 339 | |
|
338 | 340 | c.journal_pager = Page( |
|
339 | 341 | journal, page=p, items_per_page=20, url=url_generator) |
|
340 | 342 | c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager) |
|
341 | 343 | |
|
342 | 344 | c.journal_data = render( |
|
343 | 345 | 'rhodecode:templates/journal/journal_data.mako', |
|
344 | 346 | self._get_template_context(c), self.request) |
|
345 | 347 | |
|
346 | 348 | if self.request.is_xhr: |
|
347 | 349 | return Response(c.journal_data) |
|
348 | 350 | |
|
349 | 351 | html = render( |
|
350 | 352 | 'rhodecode:templates/journal/public_journal.mako', |
|
351 | 353 | self._get_template_context(c), self.request) |
|
352 | 354 | return Response(html) |
|
353 | 355 | |
|
354 | 356 | @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED]) |
|
355 | 357 | @view_config( |
|
356 | 358 | route_name='journal_public_atom', request_method='GET', |
|
357 | 359 | renderer=None) |
|
358 | 360 | def journal_public_atom(self): |
|
359 | 361 | """ |
|
360 | 362 | Produce an atom-1.0 feed via feedgenerator module |
|
361 | 363 | """ |
|
362 | 364 | c = self.load_default_context() |
|
363 | 365 | following_repos = Session().query(UserFollowing)\ |
|
364 | 366 | .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\ |
|
365 | 367 | .options(joinedload(UserFollowing.follows_repository))\ |
|
366 | 368 | .all() |
|
367 | 369 | |
|
368 | 370 | return self._atom_feed(following_repos, c.search_term) |
|
369 | 371 | |
|
370 | 372 | @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED]) |
|
371 | 373 | @view_config( |
|
372 | 374 | route_name='journal_public_rss', request_method='GET', |
|
373 | 375 | renderer=None) |
|
374 | 376 | def journal_public_rss(self): |
|
375 | 377 | """ |
|
376 | 378 | Produce an rss2 feed via feedgenerator module |
|
377 | 379 | """ |
|
378 | 380 | c = self.load_default_context() |
|
379 | 381 | following_repos = Session().query(UserFollowing)\ |
|
380 | 382 | .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\ |
|
381 | 383 | .options(joinedload(UserFollowing.follows_repository))\ |
|
382 | 384 | .all() |
|
383 | 385 | |
|
384 | 386 | return self._rss_feed(following_repos, c.search_term) |
@@ -1,554 +1,553 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | |
|
3 | 3 | # Copyright (C) 2010-2017 RhodeCode GmbH |
|
4 | 4 | # |
|
5 | 5 | # This program is free software: you can redistribute it and/or modify |
|
6 | 6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
7 | 7 | # (only), as published by the Free Software Foundation. |
|
8 | 8 | # |
|
9 | 9 | # This program is distributed in the hope that it will be useful, |
|
10 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 | 12 | # GNU General Public License for more details. |
|
13 | 13 | # |
|
14 | 14 | # You should have received a copy of the GNU Affero General Public License |
|
15 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | 16 | # |
|
17 | 17 | # This program is dual-licensed. If you wish to learn more about the |
|
18 | 18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
19 | 19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
20 | 20 | |
|
21 | 21 | import urlparse |
|
22 | 22 | |
|
23 | 23 | import mock |
|
24 | 24 | import pytest |
|
25 | 25 | |
|
26 | 26 | from rhodecode.tests import ( |
|
27 | 27 | assert_session_flash, HG_REPO, TEST_USER_ADMIN_LOGIN, |
|
28 | 28 | no_newline_id_generator) |
|
29 | 29 | from rhodecode.tests.fixture import Fixture |
|
30 | 30 | from rhodecode.lib.auth import check_password |
|
31 | 31 | from rhodecode.lib import helpers as h |
|
32 | 32 | from rhodecode.model.auth_token import AuthTokenModel |
|
33 | from rhodecode.model import validators | |
|
34 | 33 | from rhodecode.model.db import User, Notification, UserApiKeys |
|
35 | 34 | from rhodecode.model.meta import Session |
|
36 | 35 | |
|
37 | 36 | fixture = Fixture() |
|
38 | 37 | |
|
39 | 38 | whitelist_view = ['RepoCommitsView:repo_commit_raw'] |
|
40 | 39 | |
|
41 | 40 | |
|
42 | 41 | def route_path(name, params=None, **kwargs): |
|
43 | 42 | import urllib |
|
44 | 43 | from rhodecode.apps._base import ADMIN_PREFIX |
|
45 | 44 | |
|
46 | 45 | base_url = { |
|
47 | 46 | 'login': ADMIN_PREFIX + '/login', |
|
48 | 47 | 'logout': ADMIN_PREFIX + '/logout', |
|
49 | 48 | 'register': ADMIN_PREFIX + '/register', |
|
50 | 49 | 'reset_password': |
|
51 | 50 | ADMIN_PREFIX + '/password_reset', |
|
52 | 51 | 'reset_password_confirmation': |
|
53 | 52 | ADMIN_PREFIX + '/password_reset_confirmation', |
|
54 | 53 | |
|
55 | 54 | 'admin_permissions_application': |
|
56 | 55 | ADMIN_PREFIX + '/permissions/application', |
|
57 | 56 | 'admin_permissions_application_update': |
|
58 | 57 | ADMIN_PREFIX + '/permissions/application/update', |
|
59 | 58 | |
|
60 | 59 | 'repo_commit_raw': '/{repo_name}/raw-changeset/{commit_id}' |
|
61 | 60 | |
|
62 | 61 | }[name].format(**kwargs) |
|
63 | 62 | |
|
64 | 63 | if params: |
|
65 | 64 | base_url = '{}?{}'.format(base_url, urllib.urlencode(params)) |
|
66 | 65 | return base_url |
|
67 | 66 | |
|
68 | 67 | |
|
69 | 68 | @pytest.mark.usefixtures('app') |
|
70 | 69 | class TestLoginController(object): |
|
71 | 70 | destroy_users = set() |
|
72 | 71 | |
|
73 | 72 | @classmethod |
|
74 | 73 | def teardown_class(cls): |
|
75 | 74 | fixture.destroy_users(cls.destroy_users) |
|
76 | 75 | |
|
77 | 76 | def teardown_method(self, method): |
|
78 | 77 | for n in Notification.query().all(): |
|
79 | 78 | Session().delete(n) |
|
80 | 79 | |
|
81 | 80 | Session().commit() |
|
82 | 81 | assert Notification.query().all() == [] |
|
83 | 82 | |
|
84 | 83 | def test_index(self): |
|
85 | 84 | response = self.app.get(route_path('login')) |
|
86 | 85 | assert response.status == '200 OK' |
|
87 | 86 | # Test response... |
|
88 | 87 | |
|
89 | 88 | def test_login_admin_ok(self): |
|
90 | 89 | response = self.app.post(route_path('login'), |
|
91 | 90 | {'username': 'test_admin', |
|
92 | 91 | 'password': 'test12'}) |
|
93 | 92 | assert response.status == '302 Found' |
|
94 | 93 | session = response.get_session_from_response() |
|
95 | 94 | username = session['rhodecode_user'].get('username') |
|
96 | 95 | assert username == 'test_admin' |
|
97 | 96 | response = response.follow() |
|
98 | 97 | response.mustcontain('/%s' % HG_REPO) |
|
99 | 98 | |
|
100 | 99 | def test_login_regular_ok(self): |
|
101 | 100 | response = self.app.post(route_path('login'), |
|
102 | 101 | {'username': 'test_regular', |
|
103 | 102 | 'password': 'test12'}) |
|
104 | 103 | |
|
105 | 104 | assert response.status == '302 Found' |
|
106 | 105 | session = response.get_session_from_response() |
|
107 | 106 | username = session['rhodecode_user'].get('username') |
|
108 | 107 | assert username == 'test_regular' |
|
109 | 108 | response = response.follow() |
|
110 | 109 | response.mustcontain('/%s' % HG_REPO) |
|
111 | 110 | |
|
112 | 111 | def test_login_ok_came_from(self): |
|
113 | 112 | test_came_from = '/_admin/users?branch=stable' |
|
114 | 113 | _url = '{}?came_from={}'.format(route_path('login'), test_came_from) |
|
115 | 114 | response = self.app.post( |
|
116 | 115 | _url, {'username': 'test_admin', 'password': 'test12'}) |
|
117 | 116 | assert response.status == '302 Found' |
|
118 | 117 | assert 'branch=stable' in response.location |
|
119 | 118 | response = response.follow() |
|
120 | 119 | |
|
121 | 120 | assert response.status == '200 OK' |
|
122 | 121 | response.mustcontain('Users administration') |
|
123 | 122 | |
|
124 | 123 | def test_redirect_to_login_with_get_args(self): |
|
125 | 124 | with fixture.anon_access(False): |
|
126 | 125 | kwargs = {'branch': 'stable'} |
|
127 | 126 | response = self.app.get( |
|
128 | 127 | h.route_path('repo_summary', repo_name=HG_REPO, _query=kwargs)) |
|
129 | 128 | assert response.status == '302 Found' |
|
130 | 129 | |
|
131 | 130 | response_query = urlparse.parse_qsl(response.location) |
|
132 | 131 | assert 'branch=stable' in response_query[0][1] |
|
133 | 132 | |
|
134 | 133 | def test_login_form_with_get_args(self): |
|
135 | 134 | _url = '{}?came_from=/_admin/users,branch=stable'.format(route_path('login')) |
|
136 | 135 | response = self.app.get(_url) |
|
137 | 136 | assert 'branch%3Dstable' in response.form.action |
|
138 | 137 | |
|
139 | 138 | @pytest.mark.parametrize("url_came_from", [ |
|
140 | 139 | 'data:text/html,<script>window.alert("xss")</script>', |
|
141 | 140 | 'mailto:test@rhodecode.org', |
|
142 | 141 | 'file:///etc/passwd', |
|
143 | 142 | 'ftp://some.ftp.server', |
|
144 | 143 | 'http://other.domain', |
|
145 | 144 | '/\r\nX-Forwarded-Host: http://example.org', |
|
146 | 145 | ], ids=no_newline_id_generator) |
|
147 | 146 | def test_login_bad_came_froms(self, url_came_from): |
|
148 | 147 | _url = '{}?came_from={}'.format(route_path('login'), url_came_from) |
|
149 | 148 | response = self.app.post( |
|
150 | 149 | _url, |
|
151 | 150 | {'username': 'test_admin', 'password': 'test12'}) |
|
152 | 151 | assert response.status == '302 Found' |
|
153 | 152 | response = response.follow() |
|
154 | 153 | assert response.status == '200 OK' |
|
155 | 154 | assert response.request.path == '/' |
|
156 | 155 | |
|
157 | 156 | def test_login_short_password(self): |
|
158 | 157 | response = self.app.post(route_path('login'), |
|
159 | 158 | {'username': 'test_admin', |
|
160 | 159 | 'password': 'as'}) |
|
161 | 160 | assert response.status == '200 OK' |
|
162 | 161 | |
|
163 | 162 | response.mustcontain('Enter 3 characters or more') |
|
164 | 163 | |
|
165 | 164 | def test_login_wrong_non_ascii_password(self, user_regular): |
|
166 | 165 | response = self.app.post( |
|
167 | 166 | route_path('login'), |
|
168 | 167 | {'username': user_regular.username, |
|
169 | 168 | 'password': u'invalid-non-asci\xe4'.encode('utf8')}) |
|
170 | 169 | |
|
171 | 170 | response.mustcontain('invalid user name') |
|
172 | 171 | response.mustcontain('invalid password') |
|
173 | 172 | |
|
174 | 173 | def test_login_with_non_ascii_password(self, user_util): |
|
175 | 174 | password = u'valid-non-ascii\xe4' |
|
176 | 175 | user = user_util.create_user(password=password) |
|
177 | 176 | response = self.app.post( |
|
178 | 177 | route_path('login'), |
|
179 | 178 | {'username': user.username, |
|
180 | 179 | 'password': password.encode('utf-8')}) |
|
181 | 180 | assert response.status_code == 302 |
|
182 | 181 | |
|
183 | 182 | def test_login_wrong_username_password(self): |
|
184 | 183 | response = self.app.post(route_path('login'), |
|
185 | 184 | {'username': 'error', |
|
186 | 185 | 'password': 'test12'}) |
|
187 | 186 | |
|
188 | 187 | response.mustcontain('invalid user name') |
|
189 | 188 | response.mustcontain('invalid password') |
|
190 | 189 | |
|
191 | 190 | def test_login_admin_ok_password_migration(self, real_crypto_backend): |
|
192 | 191 | from rhodecode.lib import auth |
|
193 | 192 | |
|
194 | 193 | # create new user, with sha256 password |
|
195 | 194 | temp_user = 'test_admin_sha256' |
|
196 | 195 | user = fixture.create_user(temp_user) |
|
197 | 196 | user.password = auth._RhodeCodeCryptoSha256().hash_create( |
|
198 | 197 | b'test123') |
|
199 | 198 | Session().add(user) |
|
200 | 199 | Session().commit() |
|
201 | 200 | self.destroy_users.add(temp_user) |
|
202 | 201 | response = self.app.post(route_path('login'), |
|
203 | 202 | {'username': temp_user, |
|
204 | 203 | 'password': 'test123'}) |
|
205 | 204 | |
|
206 | 205 | assert response.status == '302 Found' |
|
207 | 206 | session = response.get_session_from_response() |
|
208 | 207 | username = session['rhodecode_user'].get('username') |
|
209 | 208 | assert username == temp_user |
|
210 | 209 | response = response.follow() |
|
211 | 210 | response.mustcontain('/%s' % HG_REPO) |
|
212 | 211 | |
|
213 | 212 | # new password should be bcrypted, after log-in and transfer |
|
214 | 213 | user = User.get_by_username(temp_user) |
|
215 | 214 | assert user.password.startswith('$') |
|
216 | 215 | |
|
217 | 216 | # REGISTRATIONS |
|
218 | 217 | def test_register(self): |
|
219 | 218 | response = self.app.get(route_path('register')) |
|
220 | 219 | response.mustcontain('Create an Account') |
|
221 | 220 | |
|
222 | 221 | def test_register_err_same_username(self): |
|
223 | 222 | uname = 'test_admin' |
|
224 | 223 | response = self.app.post( |
|
225 | 224 | route_path('register'), |
|
226 | 225 | { |
|
227 | 226 | 'username': uname, |
|
228 | 227 | 'password': 'test12', |
|
229 | 228 | 'password_confirmation': 'test12', |
|
230 | 229 | 'email': 'goodmail@domain.com', |
|
231 | 230 | 'firstname': 'test', |
|
232 | 231 | 'lastname': 'test' |
|
233 | 232 | } |
|
234 | 233 | ) |
|
235 | 234 | |
|
236 | 235 | assertr = response.assert_response() |
|
237 | msg = validators.ValidUsername()._messages['username_exists'] | |
|
236 | msg = '???' | |
|
238 | 237 | msg = msg % {'username': uname} |
|
239 | 238 | assertr.element_contains('#username+.error-message', msg) |
|
240 | 239 | |
|
241 | 240 | def test_register_err_same_email(self): |
|
242 | 241 | response = self.app.post( |
|
243 | 242 | route_path('register'), |
|
244 | 243 | { |
|
245 | 244 | 'username': 'test_admin_0', |
|
246 | 245 | 'password': 'test12', |
|
247 | 246 | 'password_confirmation': 'test12', |
|
248 | 247 | 'email': 'test_admin@mail.com', |
|
249 | 248 | 'firstname': 'test', |
|
250 | 249 | 'lastname': 'test' |
|
251 | 250 | } |
|
252 | 251 | ) |
|
253 | 252 | |
|
254 | 253 | assertr = response.assert_response() |
|
255 | msg = validators.UniqSystemEmail()()._messages['email_taken'] | |
|
254 | msg = '???' | |
|
256 | 255 | assertr.element_contains('#email+.error-message', msg) |
|
257 | 256 | |
|
258 | 257 | def test_register_err_same_email_case_sensitive(self): |
|
259 | 258 | response = self.app.post( |
|
260 | 259 | route_path('register'), |
|
261 | 260 | { |
|
262 | 261 | 'username': 'test_admin_1', |
|
263 | 262 | 'password': 'test12', |
|
264 | 263 | 'password_confirmation': 'test12', |
|
265 | 264 | 'email': 'TesT_Admin@mail.COM', |
|
266 | 265 | 'firstname': 'test', |
|
267 | 266 | 'lastname': 'test' |
|
268 | 267 | } |
|
269 | 268 | ) |
|
270 | 269 | assertr = response.assert_response() |
|
271 | msg = validators.UniqSystemEmail()()._messages['email_taken'] | |
|
270 | msg = '???' | |
|
272 | 271 | assertr.element_contains('#email+.error-message', msg) |
|
273 | 272 | |
|
274 | 273 | def test_register_err_wrong_data(self): |
|
275 | 274 | response = self.app.post( |
|
276 | 275 | route_path('register'), |
|
277 | 276 | { |
|
278 | 277 | 'username': 'xs', |
|
279 | 278 | 'password': 'test', |
|
280 | 279 | 'password_confirmation': 'test', |
|
281 | 280 | 'email': 'goodmailm', |
|
282 | 281 | 'firstname': 'test', |
|
283 | 282 | 'lastname': 'test' |
|
284 | 283 | } |
|
285 | 284 | ) |
|
286 | 285 | assert response.status == '200 OK' |
|
287 | 286 | response.mustcontain('An email address must contain a single @') |
|
288 | 287 | response.mustcontain('Enter a value 6 characters long or more') |
|
289 | 288 | |
|
290 | 289 | def test_register_err_username(self): |
|
291 | 290 | response = self.app.post( |
|
292 | 291 | route_path('register'), |
|
293 | 292 | { |
|
294 | 293 | 'username': 'error user', |
|
295 | 294 | 'password': 'test12', |
|
296 | 295 | 'password_confirmation': 'test12', |
|
297 | 296 | 'email': 'goodmailm', |
|
298 | 297 | 'firstname': 'test', |
|
299 | 298 | 'lastname': 'test' |
|
300 | 299 | } |
|
301 | 300 | ) |
|
302 | 301 | |
|
303 | 302 | response.mustcontain('An email address must contain a single @') |
|
304 | 303 | response.mustcontain( |
|
305 | 304 | 'Username may only contain ' |
|
306 | 305 | 'alphanumeric characters underscores, ' |
|
307 | 306 | 'periods or dashes and must begin with ' |
|
308 | 307 | 'alphanumeric character') |
|
309 | 308 | |
|
310 | 309 | def test_register_err_case_sensitive(self): |
|
311 | 310 | usr = 'Test_Admin' |
|
312 | 311 | response = self.app.post( |
|
313 | 312 | route_path('register'), |
|
314 | 313 | { |
|
315 | 314 | 'username': usr, |
|
316 | 315 | 'password': 'test12', |
|
317 | 316 | 'password_confirmation': 'test12', |
|
318 | 317 | 'email': 'goodmailm', |
|
319 | 318 | 'firstname': 'test', |
|
320 | 319 | 'lastname': 'test' |
|
321 | 320 | } |
|
322 | 321 | ) |
|
323 | 322 | |
|
324 | 323 | assertr = response.assert_response() |
|
325 | msg = validators.ValidUsername()._messages['username_exists'] | |
|
324 | msg = '???' | |
|
326 | 325 | msg = msg % {'username': usr} |
|
327 | 326 | assertr.element_contains('#username+.error-message', msg) |
|
328 | 327 | |
|
329 | 328 | def test_register_special_chars(self): |
|
330 | 329 | response = self.app.post( |
|
331 | 330 | route_path('register'), |
|
332 | 331 | { |
|
333 | 332 | 'username': 'xxxaxn', |
|
334 | 333 | 'password': 'ąćźżąśśśś', |
|
335 | 334 | 'password_confirmation': 'ąćźżąśśśś', |
|
336 | 335 | 'email': 'goodmailm@test.plx', |
|
337 | 336 | 'firstname': 'test', |
|
338 | 337 | 'lastname': 'test' |
|
339 | 338 | } |
|
340 | 339 | ) |
|
341 | 340 | |
|
342 | msg = validators.ValidPassword()._messages['invalid_password'] | |
|
341 | msg = '???' | |
|
343 | 342 | response.mustcontain(msg) |
|
344 | 343 | |
|
345 | 344 | def test_register_password_mismatch(self): |
|
346 | 345 | response = self.app.post( |
|
347 | 346 | route_path('register'), |
|
348 | 347 | { |
|
349 | 348 | 'username': 'xs', |
|
350 | 349 | 'password': '123qwe', |
|
351 | 350 | 'password_confirmation': 'qwe123', |
|
352 | 351 | 'email': 'goodmailm@test.plxa', |
|
353 | 352 | 'firstname': 'test', |
|
354 | 353 | 'lastname': 'test' |
|
355 | 354 | } |
|
356 | 355 | ) |
|
357 | msg = validators.ValidPasswordsMatch()._messages['password_mismatch'] | |
|
356 | msg = '???' | |
|
358 | 357 | response.mustcontain(msg) |
|
359 | 358 | |
|
360 | 359 | def test_register_ok(self): |
|
361 | 360 | username = 'test_regular4' |
|
362 | 361 | password = 'qweqwe' |
|
363 | 362 | email = 'marcin@test.com' |
|
364 | 363 | name = 'testname' |
|
365 | 364 | lastname = 'testlastname' |
|
366 | 365 | |
|
367 | 366 | response = self.app.post( |
|
368 | 367 | route_path('register'), |
|
369 | 368 | { |
|
370 | 369 | 'username': username, |
|
371 | 370 | 'password': password, |
|
372 | 371 | 'password_confirmation': password, |
|
373 | 372 | 'email': email, |
|
374 | 373 | 'firstname': name, |
|
375 | 374 | 'lastname': lastname, |
|
376 | 375 | 'admin': True |
|
377 | 376 | } |
|
378 | 377 | ) # This should be overriden |
|
379 | 378 | assert response.status == '302 Found' |
|
380 | 379 | assert_session_flash( |
|
381 | 380 | response, 'You have successfully registered with RhodeCode') |
|
382 | 381 | |
|
383 | 382 | ret = Session().query(User).filter( |
|
384 | 383 | User.username == 'test_regular4').one() |
|
385 | 384 | assert ret.username == username |
|
386 | 385 | assert check_password(password, ret.password) |
|
387 | 386 | assert ret.email == email |
|
388 | 387 | assert ret.name == name |
|
389 | 388 | assert ret.lastname == lastname |
|
390 | 389 | assert ret.auth_tokens is not None |
|
391 | 390 | assert not ret.admin |
|
392 | 391 | |
|
393 | 392 | def test_forgot_password_wrong_mail(self): |
|
394 | 393 | bad_email = 'marcin@wrongmail.org' |
|
395 | 394 | response = self.app.post( |
|
396 | 395 | route_path('reset_password'), {'email': bad_email, } |
|
397 | 396 | ) |
|
398 | 397 | assert_session_flash(response, |
|
399 | 398 | 'If such email exists, a password reset link was sent to it.') |
|
400 | 399 | |
|
401 | 400 | def test_forgot_password(self, user_util): |
|
402 | 401 | response = self.app.get(route_path('reset_password')) |
|
403 | 402 | assert response.status == '200 OK' |
|
404 | 403 | |
|
405 | 404 | user = user_util.create_user() |
|
406 | 405 | user_id = user.user_id |
|
407 | 406 | email = user.email |
|
408 | 407 | |
|
409 | 408 | response = self.app.post(route_path('reset_password'), {'email': email, }) |
|
410 | 409 | |
|
411 | 410 | assert_session_flash(response, |
|
412 | 411 | 'If such email exists, a password reset link was sent to it.') |
|
413 | 412 | |
|
414 | 413 | # BAD KEY |
|
415 | 414 | confirm_url = '{}?key={}'.format(route_path('reset_password_confirmation'), 'badkey') |
|
416 | 415 | response = self.app.get(confirm_url) |
|
417 | 416 | assert response.status == '302 Found' |
|
418 | 417 | assert response.location.endswith(route_path('reset_password')) |
|
419 | 418 | assert_session_flash(response, 'Given reset token is invalid') |
|
420 | 419 | |
|
421 | 420 | response.follow() # cleanup flash |
|
422 | 421 | |
|
423 | 422 | # GOOD KEY |
|
424 | 423 | key = UserApiKeys.query()\ |
|
425 | 424 | .filter(UserApiKeys.user_id == user_id)\ |
|
426 | 425 | .filter(UserApiKeys.role == UserApiKeys.ROLE_PASSWORD_RESET)\ |
|
427 | 426 | .first() |
|
428 | 427 | |
|
429 | 428 | assert key |
|
430 | 429 | |
|
431 | 430 | confirm_url = '{}?key={}'.format(route_path('reset_password_confirmation'), key.api_key) |
|
432 | 431 | response = self.app.get(confirm_url) |
|
433 | 432 | assert response.status == '302 Found' |
|
434 | 433 | assert response.location.endswith(route_path('login')) |
|
435 | 434 | |
|
436 | 435 | assert_session_flash( |
|
437 | 436 | response, |
|
438 | 437 | 'Your password reset was successful, ' |
|
439 | 438 | 'a new password has been sent to your email') |
|
440 | 439 | |
|
441 | 440 | response.follow() |
|
442 | 441 | |
|
443 | 442 | def _get_api_whitelist(self, values=None): |
|
444 | 443 | config = {'api_access_controllers_whitelist': values or []} |
|
445 | 444 | return config |
|
446 | 445 | |
|
447 | 446 | @pytest.mark.parametrize("test_name, auth_token", [ |
|
448 | 447 | ('none', None), |
|
449 | 448 | ('empty_string', ''), |
|
450 | 449 | ('fake_number', '123456'), |
|
451 | 450 | ('proper_auth_token', None) |
|
452 | 451 | ]) |
|
453 | 452 | def test_access_not_whitelisted_page_via_auth_token( |
|
454 | 453 | self, test_name, auth_token, user_admin): |
|
455 | 454 | |
|
456 | 455 | whitelist = self._get_api_whitelist([]) |
|
457 | 456 | with mock.patch.dict('rhodecode.CONFIG', whitelist): |
|
458 | 457 | assert [] == whitelist['api_access_controllers_whitelist'] |
|
459 | 458 | if test_name == 'proper_auth_token': |
|
460 | 459 | # use builtin if api_key is None |
|
461 | 460 | auth_token = user_admin.api_key |
|
462 | 461 | |
|
463 | 462 | with fixture.anon_access(False): |
|
464 | 463 | self.app.get( |
|
465 | 464 | route_path('repo_commit_raw', |
|
466 | 465 | repo_name=HG_REPO, commit_id='tip', |
|
467 | 466 | params=dict(api_key=auth_token)), |
|
468 | 467 | status=302) |
|
469 | 468 | |
|
470 | 469 | @pytest.mark.parametrize("test_name, auth_token, code", [ |
|
471 | 470 | ('none', None, 302), |
|
472 | 471 | ('empty_string', '', 302), |
|
473 | 472 | ('fake_number', '123456', 302), |
|
474 | 473 | ('proper_auth_token', None, 200) |
|
475 | 474 | ]) |
|
476 | 475 | def test_access_whitelisted_page_via_auth_token( |
|
477 | 476 | self, test_name, auth_token, code, user_admin): |
|
478 | 477 | |
|
479 | 478 | whitelist = self._get_api_whitelist(whitelist_view) |
|
480 | 479 | |
|
481 | 480 | with mock.patch.dict('rhodecode.CONFIG', whitelist): |
|
482 | 481 | assert whitelist_view == whitelist['api_access_controllers_whitelist'] |
|
483 | 482 | |
|
484 | 483 | if test_name == 'proper_auth_token': |
|
485 | 484 | auth_token = user_admin.api_key |
|
486 | 485 | assert auth_token |
|
487 | 486 | |
|
488 | 487 | with fixture.anon_access(False): |
|
489 | 488 | self.app.get( |
|
490 | 489 | route_path('repo_commit_raw', |
|
491 | 490 | repo_name=HG_REPO, commit_id='tip', |
|
492 | 491 | params=dict(api_key=auth_token)), |
|
493 | 492 | status=code) |
|
494 | 493 | |
|
495 | 494 | @pytest.mark.parametrize("test_name, auth_token, code", [ |
|
496 | 495 | ('proper_auth_token', None, 200), |
|
497 | 496 | ('wrong_auth_token', '123456', 302), |
|
498 | 497 | ]) |
|
499 | 498 | def test_access_whitelisted_page_via_auth_token_bound_to_token( |
|
500 | 499 | self, test_name, auth_token, code, user_admin): |
|
501 | 500 | |
|
502 | 501 | expected_token = auth_token |
|
503 | 502 | if test_name == 'proper_auth_token': |
|
504 | 503 | auth_token = user_admin.api_key |
|
505 | 504 | expected_token = auth_token |
|
506 | 505 | assert auth_token |
|
507 | 506 | |
|
508 | 507 | whitelist = self._get_api_whitelist([ |
|
509 | 508 | 'RepoCommitsView:repo_commit_raw@{}'.format(expected_token)]) |
|
510 | 509 | |
|
511 | 510 | with mock.patch.dict('rhodecode.CONFIG', whitelist): |
|
512 | 511 | |
|
513 | 512 | with fixture.anon_access(False): |
|
514 | 513 | self.app.get( |
|
515 | 514 | route_path('repo_commit_raw', |
|
516 | 515 | repo_name=HG_REPO, commit_id='tip', |
|
517 | 516 | params=dict(api_key=auth_token)), |
|
518 | 517 | status=code) |
|
519 | 518 | |
|
520 | 519 | def test_access_page_via_extra_auth_token(self): |
|
521 | 520 | whitelist = self._get_api_whitelist(whitelist_view) |
|
522 | 521 | with mock.patch.dict('rhodecode.CONFIG', whitelist): |
|
523 | 522 | assert whitelist_view == \ |
|
524 | 523 | whitelist['api_access_controllers_whitelist'] |
|
525 | 524 | |
|
526 | 525 | new_auth_token = AuthTokenModel().create( |
|
527 | 526 | TEST_USER_ADMIN_LOGIN, 'test') |
|
528 | 527 | Session().commit() |
|
529 | 528 | with fixture.anon_access(False): |
|
530 | 529 | self.app.get( |
|
531 | 530 | route_path('repo_commit_raw', |
|
532 | 531 | repo_name=HG_REPO, commit_id='tip', |
|
533 | 532 | params=dict(api_key=new_auth_token.api_key)), |
|
534 | 533 | status=200) |
|
535 | 534 | |
|
536 | 535 | def test_access_page_via_expired_auth_token(self): |
|
537 | 536 | whitelist = self._get_api_whitelist(whitelist_view) |
|
538 | 537 | with mock.patch.dict('rhodecode.CONFIG', whitelist): |
|
539 | 538 | assert whitelist_view == \ |
|
540 | 539 | whitelist['api_access_controllers_whitelist'] |
|
541 | 540 | |
|
542 | 541 | new_auth_token = AuthTokenModel().create( |
|
543 | 542 | TEST_USER_ADMIN_LOGIN, 'test') |
|
544 | 543 | Session().commit() |
|
545 | 544 | # patch the api key and make it expired |
|
546 | 545 | new_auth_token.expires = 0 |
|
547 | 546 | Session().add(new_auth_token) |
|
548 | 547 | Session().commit() |
|
549 | 548 | with fixture.anon_access(False): |
|
550 | 549 | self.app.get( |
|
551 | 550 | route_path('repo_commit_raw', |
|
552 | 551 | repo_name=HG_REPO, commit_id='tip', |
|
553 | 552 | params=dict(api_key=new_auth_token.api_key)), |
|
554 | 553 | status=302) |
@@ -1,427 +1,426 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | |
|
3 | 3 | # Copyright (C) 2016-2017 RhodeCode GmbH |
|
4 | 4 | # |
|
5 | 5 | # This program is free software: you can redistribute it and/or modify |
|
6 | 6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
7 | 7 | # (only), as published by the Free Software Foundation. |
|
8 | 8 | # |
|
9 | 9 | # This program is distributed in the hope that it will be useful, |
|
10 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 | 12 | # GNU General Public License for more details. |
|
13 | 13 | # |
|
14 | 14 | # You should have received a copy of the GNU Affero General Public License |
|
15 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | 16 | # |
|
17 | 17 | # This program is dual-licensed. If you wish to learn more about the |
|
18 | 18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
19 | 19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
20 | 20 | |
|
21 | 21 | import time |
|
22 | 22 | import collections |
|
23 | 23 | import datetime |
|
24 | 24 | import formencode |
|
25 | 25 | import formencode.htmlfill |
|
26 | 26 | import logging |
|
27 | 27 | import urlparse |
|
28 | 28 | |
|
29 | 29 | from pyramid.httpexceptions import HTTPFound |
|
30 | 30 | from pyramid.view import view_config |
|
31 | 31 | from recaptcha.client.captcha import submit |
|
32 | 32 | |
|
33 | 33 | from rhodecode.apps._base import BaseAppView |
|
34 | 34 | from rhodecode.authentication.base import authenticate, HTTP_TYPE |
|
35 | 35 | from rhodecode.events import UserRegistered |
|
36 | 36 | from rhodecode.lib import helpers as h |
|
37 | 37 | from rhodecode.lib import audit_logger |
|
38 | 38 | from rhodecode.lib.auth import ( |
|
39 | 39 | AuthUser, HasPermissionAnyDecorator, CSRFRequired) |
|
40 | 40 | from rhodecode.lib.base import get_ip_addr |
|
41 | 41 | from rhodecode.lib.exceptions import UserCreationError |
|
42 | 42 | from rhodecode.lib.utils2 import safe_str |
|
43 | 43 | from rhodecode.model.db import User, UserApiKeys |
|
44 | 44 | from rhodecode.model.forms import LoginForm, RegisterForm, PasswordResetForm |
|
45 | 45 | from rhodecode.model.meta import Session |
|
46 | 46 | from rhodecode.model.auth_token import AuthTokenModel |
|
47 | 47 | from rhodecode.model.settings import SettingsModel |
|
48 | 48 | from rhodecode.model.user import UserModel |
|
49 | 49 | from rhodecode.translation import _ |
|
50 | 50 | |
|
51 | 51 | |
|
52 | 52 | log = logging.getLogger(__name__) |
|
53 | 53 | |
|
54 | 54 | CaptchaData = collections.namedtuple( |
|
55 | 55 | 'CaptchaData', 'active, private_key, public_key') |
|
56 | 56 | |
|
57 | 57 | |
|
58 | 58 | def _store_user_in_session(session, username, remember=False): |
|
59 | 59 | user = User.get_by_username(username, case_insensitive=True) |
|
60 | 60 | auth_user = AuthUser(user.user_id) |
|
61 | 61 | auth_user.set_authenticated() |
|
62 | 62 | cs = auth_user.get_cookie_store() |
|
63 | 63 | session['rhodecode_user'] = cs |
|
64 | 64 | user.update_lastlogin() |
|
65 | 65 | Session().commit() |
|
66 | 66 | |
|
67 | 67 | # If they want to be remembered, update the cookie |
|
68 | 68 | if remember: |
|
69 | 69 | _year = (datetime.datetime.now() + |
|
70 | 70 | datetime.timedelta(seconds=60 * 60 * 24 * 365)) |
|
71 | 71 | session._set_cookie_expires(_year) |
|
72 | 72 | |
|
73 | 73 | session.save() |
|
74 | 74 | |
|
75 | 75 | safe_cs = cs.copy() |
|
76 | 76 | safe_cs['password'] = '****' |
|
77 | 77 | log.info('user %s is now authenticated and stored in ' |
|
78 | 78 | 'session, session attrs %s', username, safe_cs) |
|
79 | 79 | |
|
80 | 80 | # dumps session attrs back to cookie |
|
81 | 81 | session._update_cookie_out() |
|
82 | 82 | # we set new cookie |
|
83 | 83 | headers = None |
|
84 | 84 | if session.request['set_cookie']: |
|
85 | 85 | # send set-cookie headers back to response to update cookie |
|
86 | 86 | headers = [('Set-Cookie', session.request['cookie_out'])] |
|
87 | 87 | return headers |
|
88 | 88 | |
|
89 | 89 | |
|
90 | 90 | def get_came_from(request): |
|
91 | 91 | came_from = safe_str(request.GET.get('came_from', '')) |
|
92 | 92 | parsed = urlparse.urlparse(came_from) |
|
93 | 93 | allowed_schemes = ['http', 'https'] |
|
94 | 94 | default_came_from = h.route_path('home') |
|
95 | 95 | if parsed.scheme and parsed.scheme not in allowed_schemes: |
|
96 | 96 | log.error('Suspicious URL scheme detected %s for url %s' % |
|
97 | 97 | (parsed.scheme, parsed)) |
|
98 | 98 | came_from = default_came_from |
|
99 | 99 | elif parsed.netloc and request.host != parsed.netloc: |
|
100 | 100 | log.error('Suspicious NETLOC detected %s for url %s server url ' |
|
101 | 101 | 'is: %s' % (parsed.netloc, parsed, request.host)) |
|
102 | 102 | came_from = default_came_from |
|
103 | 103 | elif any(bad_str in parsed.path for bad_str in ('\r', '\n')): |
|
104 | 104 | log.error('Header injection detected `%s` for url %s server url ' % |
|
105 | 105 | (parsed.path, parsed)) |
|
106 | 106 | came_from = default_came_from |
|
107 | 107 | |
|
108 | 108 | return came_from or default_came_from |
|
109 | 109 | |
|
110 | 110 | |
|
111 | 111 | class LoginView(BaseAppView): |
|
112 | 112 | |
|
113 | 113 | def load_default_context(self): |
|
114 | 114 | c = self._get_local_tmpl_context() |
|
115 | 115 | c.came_from = get_came_from(self.request) |
|
116 | self._register_global_c(c) | |
|
116 | ||
|
117 | 117 | return c |
|
118 | 118 | |
|
119 | 119 | def _get_captcha_data(self): |
|
120 | 120 | settings = SettingsModel().get_all_settings() |
|
121 | 121 | private_key = settings.get('rhodecode_captcha_private_key') |
|
122 | 122 | public_key = settings.get('rhodecode_captcha_public_key') |
|
123 | 123 | active = bool(private_key) |
|
124 | 124 | return CaptchaData( |
|
125 | 125 | active=active, private_key=private_key, public_key=public_key) |
|
126 | 126 | |
|
127 | 127 | @view_config( |
|
128 | 128 | route_name='login', request_method='GET', |
|
129 | 129 | renderer='rhodecode:templates/login.mako') |
|
130 | 130 | def login(self): |
|
131 | 131 | c = self.load_default_context() |
|
132 | 132 | auth_user = self._rhodecode_user |
|
133 | 133 | |
|
134 | 134 | # redirect if already logged in |
|
135 | 135 | if (auth_user.is_authenticated and |
|
136 | 136 | not auth_user.is_default and auth_user.ip_allowed): |
|
137 | 137 | raise HTTPFound(c.came_from) |
|
138 | 138 | |
|
139 | 139 | # check if we use headers plugin, and try to login using it. |
|
140 | 140 | try: |
|
141 | 141 | log.debug('Running PRE-AUTH for headers based authentication') |
|
142 | 142 | auth_info = authenticate( |
|
143 | 143 | '', '', self.request.environ, HTTP_TYPE, skip_missing=True) |
|
144 | 144 | if auth_info: |
|
145 | 145 | headers = _store_user_in_session( |
|
146 | 146 | self.session, auth_info.get('username')) |
|
147 | 147 | raise HTTPFound(c.came_from, headers=headers) |
|
148 | 148 | except UserCreationError as e: |
|
149 | 149 | log.error(e) |
|
150 | 150 | self.session.flash(e, queue='error') |
|
151 | 151 | |
|
152 | 152 | return self._get_template_context(c) |
|
153 | 153 | |
|
154 | 154 | @view_config( |
|
155 | 155 | route_name='login', request_method='POST', |
|
156 | 156 | renderer='rhodecode:templates/login.mako') |
|
157 | 157 | def login_post(self): |
|
158 | 158 | c = self.load_default_context() |
|
159 | 159 | |
|
160 | login_form = LoginForm()() | |
|
160 | login_form = LoginForm(self.request.translate)() | |
|
161 | 161 | |
|
162 | 162 | try: |
|
163 | 163 | self.session.invalidate() |
|
164 | 164 | form_result = login_form.to_python(self.request.POST) |
|
165 | 165 | # form checks for username/password, now we're authenticated |
|
166 | 166 | headers = _store_user_in_session( |
|
167 | 167 | self.session, |
|
168 | 168 | username=form_result['username'], |
|
169 | 169 | remember=form_result['remember']) |
|
170 | 170 | log.debug('Redirecting to "%s" after login.', c.came_from) |
|
171 | 171 | |
|
172 | 172 | audit_user = audit_logger.UserWrap( |
|
173 | 173 | username=self.request.POST.get('username'), |
|
174 | 174 | ip_addr=self.request.remote_addr) |
|
175 | 175 | action_data = {'user_agent': self.request.user_agent} |
|
176 | 176 | audit_logger.store_web( |
|
177 | 177 | 'user.login.success', action_data=action_data, |
|
178 | 178 | user=audit_user, commit=True) |
|
179 | 179 | |
|
180 | 180 | raise HTTPFound(c.came_from, headers=headers) |
|
181 | 181 | except formencode.Invalid as errors: |
|
182 | 182 | defaults = errors.value |
|
183 | 183 | # remove password from filling in form again |
|
184 | 184 | defaults.pop('password', None) |
|
185 |
render_ctx = |
|
|
186 | render_ctx.update({ | |
|
185 | render_ctx = { | |
|
187 | 186 | 'errors': errors.error_dict, |
|
188 | 187 | 'defaults': defaults, |
|
189 |
} |
|
|
188 | } | |
|
190 | 189 | |
|
191 | 190 | audit_user = audit_logger.UserWrap( |
|
192 | 191 | username=self.request.POST.get('username'), |
|
193 | 192 | ip_addr=self.request.remote_addr) |
|
194 | 193 | action_data = {'user_agent': self.request.user_agent} |
|
195 | 194 | audit_logger.store_web( |
|
196 | 195 | 'user.login.failure', action_data=action_data, |
|
197 | 196 | user=audit_user, commit=True) |
|
198 | return render_ctx | |
|
197 | return self._get_template_context(c, **render_ctx) | |
|
199 | 198 | |
|
200 | 199 | except UserCreationError as e: |
|
201 | 200 | # headers auth or other auth functions that create users on |
|
202 | 201 | # the fly can throw this exception signaling that there's issue |
|
203 | 202 | # with user creation, explanation should be provided in |
|
204 | 203 | # Exception itself |
|
205 | 204 | self.session.flash(e, queue='error') |
|
206 | 205 | return self._get_template_context(c) |
|
207 | 206 | |
|
208 | 207 | @CSRFRequired() |
|
209 | 208 | @view_config(route_name='logout', request_method='POST') |
|
210 | 209 | def logout(self): |
|
211 | 210 | auth_user = self._rhodecode_user |
|
212 | 211 | log.info('Deleting session for user: `%s`', auth_user) |
|
213 | 212 | |
|
214 | 213 | action_data = {'user_agent': self.request.user_agent} |
|
215 | 214 | audit_logger.store_web( |
|
216 | 215 | 'user.logout', action_data=action_data, |
|
217 | 216 | user=auth_user, commit=True) |
|
218 | 217 | self.session.delete() |
|
219 | 218 | return HTTPFound(h.route_path('home')) |
|
220 | 219 | |
|
221 | 220 | @HasPermissionAnyDecorator( |
|
222 | 221 | 'hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate') |
|
223 | 222 | @view_config( |
|
224 | 223 | route_name='register', request_method='GET', |
|
225 | 224 | renderer='rhodecode:templates/register.mako',) |
|
226 | 225 | def register(self, defaults=None, errors=None): |
|
227 | 226 | c = self.load_default_context() |
|
228 | 227 | defaults = defaults or {} |
|
229 | 228 | errors = errors or {} |
|
230 | 229 | |
|
231 | 230 | settings = SettingsModel().get_all_settings() |
|
232 | 231 | register_message = settings.get('rhodecode_register_message') or '' |
|
233 | 232 | captcha = self._get_captcha_data() |
|
234 | 233 | auto_active = 'hg.register.auto_activate' in User.get_default_user()\ |
|
235 | 234 | .AuthUser().permissions['global'] |
|
236 | 235 | |
|
237 | 236 | render_ctx = self._get_template_context(c) |
|
238 | 237 | render_ctx.update({ |
|
239 | 238 | 'defaults': defaults, |
|
240 | 239 | 'errors': errors, |
|
241 | 240 | 'auto_active': auto_active, |
|
242 | 241 | 'captcha_active': captcha.active, |
|
243 | 242 | 'captcha_public_key': captcha.public_key, |
|
244 | 243 | 'register_message': register_message, |
|
245 | 244 | }) |
|
246 | 245 | return render_ctx |
|
247 | 246 | |
|
248 | 247 | @HasPermissionAnyDecorator( |
|
249 | 248 | 'hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate') |
|
250 | 249 | @view_config( |
|
251 | 250 | route_name='register', request_method='POST', |
|
252 | 251 | renderer='rhodecode:templates/register.mako') |
|
253 | 252 | def register_post(self): |
|
254 | 253 | captcha = self._get_captcha_data() |
|
255 | 254 | auto_active = 'hg.register.auto_activate' in User.get_default_user()\ |
|
256 | 255 | .AuthUser().permissions['global'] |
|
257 | 256 | |
|
258 | register_form = RegisterForm()() | |
|
257 | register_form = RegisterForm(self.request.translate)() | |
|
259 | 258 | try: |
|
260 | 259 | |
|
261 | 260 | form_result = register_form.to_python(self.request.POST) |
|
262 | 261 | form_result['active'] = auto_active |
|
263 | 262 | |
|
264 | 263 | if captcha.active: |
|
265 | 264 | response = submit( |
|
266 | 265 | self.request.POST.get('recaptcha_challenge_field'), |
|
267 | 266 | self.request.POST.get('recaptcha_response_field'), |
|
268 | 267 | private_key=captcha.private_key, |
|
269 | 268 | remoteip=get_ip_addr(self.request.environ)) |
|
270 | 269 | if not response.is_valid: |
|
271 | 270 | _value = form_result |
|
272 | 271 | _msg = _('Bad captcha') |
|
273 | 272 | error_dict = {'recaptcha_field': _msg} |
|
274 | 273 | raise formencode.Invalid(_msg, _value, None, |
|
275 | 274 | error_dict=error_dict) |
|
276 | 275 | |
|
277 | 276 | new_user = UserModel().create_registration(form_result) |
|
278 | 277 | event = UserRegistered(user=new_user, session=self.session) |
|
279 | 278 | self.request.registry.notify(event) |
|
280 | 279 | self.session.flash( |
|
281 | 280 | _('You have successfully registered with RhodeCode'), |
|
282 | 281 | queue='success') |
|
283 | 282 | Session().commit() |
|
284 | 283 | |
|
285 | 284 | redirect_ro = self.request.route_path('login') |
|
286 | 285 | raise HTTPFound(redirect_ro) |
|
287 | 286 | |
|
288 | 287 | except formencode.Invalid as errors: |
|
289 | 288 | errors.value.pop('password', None) |
|
290 | 289 | errors.value.pop('password_confirmation', None) |
|
291 | 290 | return self.register( |
|
292 | 291 | defaults=errors.value, errors=errors.error_dict) |
|
293 | 292 | |
|
294 | 293 | except UserCreationError as e: |
|
295 | 294 | # container auth or other auth functions that create users on |
|
296 | 295 | # the fly can throw this exception signaling that there's issue |
|
297 | 296 | # with user creation, explanation should be provided in |
|
298 | 297 | # Exception itself |
|
299 | 298 | self.session.flash(e, queue='error') |
|
300 | 299 | return self.register() |
|
301 | 300 | |
|
302 | 301 | @view_config( |
|
303 | 302 | route_name='reset_password', request_method=('GET', 'POST'), |
|
304 | 303 | renderer='rhodecode:templates/password_reset.mako') |
|
305 | 304 | def password_reset(self): |
|
306 | 305 | captcha = self._get_captcha_data() |
|
307 | 306 | |
|
308 | 307 | render_ctx = { |
|
309 | 308 | 'captcha_active': captcha.active, |
|
310 | 309 | 'captcha_public_key': captcha.public_key, |
|
311 | 310 | 'defaults': {}, |
|
312 | 311 | 'errors': {}, |
|
313 | 312 | } |
|
314 | 313 | |
|
315 | 314 | # always send implicit message to prevent from discovery of |
|
316 | 315 | # matching emails |
|
317 | 316 | msg = _('If such email exists, a password reset link was sent to it.') |
|
318 | 317 | |
|
319 | 318 | if self.request.POST: |
|
320 | 319 | if h.HasPermissionAny('hg.password_reset.disabled')(): |
|
321 | 320 | _email = self.request.POST.get('email', '') |
|
322 | 321 | log.error('Failed attempt to reset password for `%s`.', _email) |
|
323 | 322 | self.session.flash(_('Password reset has been disabled.'), |
|
324 | 323 | queue='error') |
|
325 | 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 | 327 | try: |
|
329 | 328 | form_result = password_reset_form.to_python( |
|
330 | 329 | self.request.POST) |
|
331 | 330 | user_email = form_result['email'] |
|
332 | 331 | |
|
333 | 332 | if captcha.active: |
|
334 | 333 | response = submit( |
|
335 | 334 | self.request.POST.get('recaptcha_challenge_field'), |
|
336 | 335 | self.request.POST.get('recaptcha_response_field'), |
|
337 | 336 | private_key=captcha.private_key, |
|
338 | 337 | remoteip=get_ip_addr(self.request.environ)) |
|
339 | 338 | if not response.is_valid: |
|
340 | 339 | _value = form_result |
|
341 | 340 | _msg = _('Bad captcha') |
|
342 | 341 | error_dict = {'recaptcha_field': _msg} |
|
343 | 342 | raise formencode.Invalid( |
|
344 | 343 | _msg, _value, None, error_dict=error_dict) |
|
345 | 344 | |
|
346 | 345 | # Generate reset URL and send mail. |
|
347 | 346 | user = User.get_by_email(user_email) |
|
348 | 347 | |
|
349 | 348 | # generate password reset token that expires in 10minutes |
|
350 | 349 | desc = 'Generated token for password reset from {}'.format( |
|
351 | 350 | datetime.datetime.now().isoformat()) |
|
352 | 351 | reset_token = AuthTokenModel().create( |
|
353 | 352 | user, lifetime=10, |
|
354 | 353 | description=desc, |
|
355 | 354 | role=UserApiKeys.ROLE_PASSWORD_RESET) |
|
356 | 355 | Session().commit() |
|
357 | 356 | |
|
358 | 357 | log.debug('Successfully created password recovery token') |
|
359 | 358 | password_reset_url = self.request.route_url( |
|
360 | 359 | 'reset_password_confirmation', |
|
361 | 360 | _query={'key': reset_token.api_key}) |
|
362 | 361 | UserModel().reset_password_link( |
|
363 | 362 | form_result, password_reset_url) |
|
364 | 363 | # Display success message and redirect. |
|
365 | 364 | self.session.flash(msg, queue='success') |
|
366 | 365 | |
|
367 | 366 | action_data = {'email': user_email, |
|
368 | 367 | 'user_agent': self.request.user_agent} |
|
369 | 368 | audit_logger.store_web( |
|
370 | 369 | 'user.password.reset_request', action_data=action_data, |
|
371 | 370 | user=self._rhodecode_user, commit=True) |
|
372 | 371 | return HTTPFound(self.request.route_path('reset_password')) |
|
373 | 372 | |
|
374 | 373 | except formencode.Invalid as errors: |
|
375 | 374 | render_ctx.update({ |
|
376 | 375 | 'defaults': errors.value, |
|
377 | 376 | 'errors': errors.error_dict, |
|
378 | 377 | }) |
|
379 | 378 | if not self.request.POST.get('email'): |
|
380 | 379 | # case of empty email, we want to report that |
|
381 | 380 | return render_ctx |
|
382 | 381 | |
|
383 | 382 | if 'recaptcha_field' in errors.error_dict: |
|
384 | 383 | # case of failed captcha |
|
385 | 384 | return render_ctx |
|
386 | 385 | |
|
387 | 386 | log.debug('faking response on invalid password reset') |
|
388 | 387 | # make this take 2s, to prevent brute forcing. |
|
389 | 388 | time.sleep(2) |
|
390 | 389 | self.session.flash(msg, queue='success') |
|
391 | 390 | return HTTPFound(self.request.route_path('reset_password')) |
|
392 | 391 | |
|
393 | 392 | return render_ctx |
|
394 | 393 | |
|
395 | 394 | @view_config(route_name='reset_password_confirmation', |
|
396 | 395 | request_method='GET') |
|
397 | 396 | def password_reset_confirmation(self): |
|
398 | 397 | |
|
399 | 398 | if self.request.GET and self.request.GET.get('key'): |
|
400 | 399 | # make this take 2s, to prevent brute forcing. |
|
401 | 400 | time.sleep(2) |
|
402 | 401 | |
|
403 | 402 | token = AuthTokenModel().get_auth_token( |
|
404 | 403 | self.request.GET.get('key')) |
|
405 | 404 | |
|
406 | 405 | # verify token is the correct role |
|
407 | 406 | if token is None or token.role != UserApiKeys.ROLE_PASSWORD_RESET: |
|
408 | 407 | log.debug('Got token with role:%s expected is %s', |
|
409 | 408 | getattr(token, 'role', 'EMPTY_TOKEN'), |
|
410 | 409 | UserApiKeys.ROLE_PASSWORD_RESET) |
|
411 | 410 | self.session.flash( |
|
412 | 411 | _('Given reset token is invalid'), queue='error') |
|
413 | 412 | return HTTPFound(self.request.route_path('reset_password')) |
|
414 | 413 | |
|
415 | 414 | try: |
|
416 | 415 | owner = token.user |
|
417 | 416 | data = {'email': owner.email, 'token': token.api_key} |
|
418 | 417 | UserModel().reset_password(data) |
|
419 | 418 | self.session.flash( |
|
420 | 419 | _('Your password reset was successful, ' |
|
421 | 420 | 'a new password has been sent to your email'), |
|
422 | 421 | queue='success') |
|
423 | 422 | except Exception as e: |
|
424 | 423 | log.error(e) |
|
425 | 424 | return HTTPFound(self.request.route_path('reset_password')) |
|
426 | 425 | |
|
427 | 426 | return HTTPFound(self.request.route_path('login')) |
@@ -1,205 +1,203 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | |
|
3 | 3 | # Copyright (C) 2016-2017 RhodeCode GmbH |
|
4 | 4 | # |
|
5 | 5 | # This program is free software: you can redistribute it and/or modify |
|
6 | 6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
7 | 7 | # (only), as published by the Free Software Foundation. |
|
8 | 8 | # |
|
9 | 9 | # This program is distributed in the hope that it will be useful, |
|
10 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 | 12 | # GNU General Public License for more details. |
|
13 | 13 | # |
|
14 | 14 | # You should have received a copy of the GNU Affero General Public License |
|
15 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | 16 | # |
|
17 | 17 | # This program is dual-licensed. If you wish to learn more about the |
|
18 | 18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
19 | 19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
20 | 20 | # -*- coding: utf-8 -*- |
|
21 | 21 | |
|
22 | 22 | # Copyright (C) 2016-2017 RhodeCode GmbH |
|
23 | 23 | # |
|
24 | 24 | # This program is free software: you can redistribute it and/or modify |
|
25 | 25 | # it under the terms of the GNU Affero General Public License, version 3 |
|
26 | 26 | # (only), as published by the Free Software Foundation. |
|
27 | 27 | # |
|
28 | 28 | # This program is distributed in the hope that it will be useful, |
|
29 | 29 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
30 | 30 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
31 | 31 | # GNU General Public License for more details. |
|
32 | 32 | # |
|
33 | 33 | # You should have received a copy of the GNU Affero General Public License |
|
34 | 34 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
35 | 35 | # |
|
36 | 36 | # This program is dual-licensed. If you wish to learn more about the |
|
37 | 37 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
38 | 38 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
39 | 39 | |
|
40 | 40 | import pytest |
|
41 | 41 | |
|
42 | 42 | from rhodecode.model.db import User |
|
43 | 43 | from rhodecode.tests import TestController, assert_session_flash |
|
44 | 44 | from rhodecode.lib import helpers as h |
|
45 | 45 | |
|
46 | 46 | |
|
47 | 47 | def route_path(name, params=None, **kwargs): |
|
48 | 48 | import urllib |
|
49 | 49 | from rhodecode.apps._base import ADMIN_PREFIX |
|
50 | 50 | |
|
51 | 51 | base_url = { |
|
52 | 52 | 'my_account_edit': ADMIN_PREFIX + '/my_account/edit', |
|
53 | 53 | 'my_account_update': ADMIN_PREFIX + '/my_account/update', |
|
54 | 54 | 'my_account_pullrequests': ADMIN_PREFIX + '/my_account/pull_requests', |
|
55 | 55 | 'my_account_pullrequests_data': ADMIN_PREFIX + '/my_account/pull_requests/data', |
|
56 | 56 | }[name].format(**kwargs) |
|
57 | 57 | |
|
58 | 58 | if params: |
|
59 | 59 | base_url = '{}?{}'.format(base_url, urllib.urlencode(params)) |
|
60 | 60 | return base_url |
|
61 | 61 | |
|
62 | 62 | |
|
63 | 63 | class TestMyAccountEdit(TestController): |
|
64 | 64 | |
|
65 | 65 | def test_my_account_edit(self): |
|
66 | 66 | self.log_user() |
|
67 | 67 | response = self.app.get(route_path('my_account_edit')) |
|
68 | 68 | |
|
69 | 69 | response.mustcontain('value="test_admin') |
|
70 | 70 | |
|
71 | 71 | @pytest.mark.backends("git", "hg") |
|
72 | 72 | def test_my_account_my_pullrequests(self, pr_util): |
|
73 | 73 | self.log_user() |
|
74 | 74 | response = self.app.get(route_path('my_account_pullrequests')) |
|
75 | 75 | response.mustcontain('There are currently no open pull ' |
|
76 | 76 | 'requests requiring your participation.') |
|
77 | 77 | |
|
78 | 78 | @pytest.mark.backends("git", "hg") |
|
79 | 79 | def test_my_account_my_pullrequests_data(self, pr_util, xhr_header): |
|
80 | 80 | self.log_user() |
|
81 | 81 | response = self.app.get(route_path('my_account_pullrequests_data'), |
|
82 | 82 | extra_environ=xhr_header) |
|
83 | 83 | assert response.json == { |
|
84 | 84 | u'data': [], u'draw': None, |
|
85 | 85 | u'recordsFiltered': 0, u'recordsTotal': 0} |
|
86 | 86 | |
|
87 | 87 | pr = pr_util.create_pull_request(title='TestMyAccountPR') |
|
88 | 88 | expected = { |
|
89 | 89 | 'author_raw': 'RhodeCode Admin', |
|
90 | 90 | 'name_raw': pr.pull_request_id |
|
91 | 91 | } |
|
92 | 92 | response = self.app.get(route_path('my_account_pullrequests_data'), |
|
93 | 93 | extra_environ=xhr_header) |
|
94 | 94 | assert response.json['recordsTotal'] == 1 |
|
95 | 95 | assert response.json['data'][0]['author_raw'] == expected['author_raw'] |
|
96 | 96 | |
|
97 | 97 | assert response.json['data'][0]['author_raw'] == expected['author_raw'] |
|
98 | 98 | assert response.json['data'][0]['name_raw'] == expected['name_raw'] |
|
99 | 99 | |
|
100 | 100 | @pytest.mark.parametrize( |
|
101 | 101 | "name, attrs", [ |
|
102 | 102 | ('firstname', {'firstname': 'new_username'}), |
|
103 | 103 | ('lastname', {'lastname': 'new_username'}), |
|
104 | 104 | ('admin', {'admin': True}), |
|
105 | 105 | ('admin', {'admin': False}), |
|
106 | 106 | ('extern_type', {'extern_type': 'ldap'}), |
|
107 | 107 | ('extern_type', {'extern_type': None}), |
|
108 | 108 | # ('extern_name', {'extern_name': 'test'}), |
|
109 | 109 | # ('extern_name', {'extern_name': None}), |
|
110 | 110 | ('active', {'active': False}), |
|
111 | 111 | ('active', {'active': True}), |
|
112 | 112 | ('email', {'email': 'some@email.com'}), |
|
113 | 113 | ]) |
|
114 | 114 | def test_my_account_update(self, name, attrs, user_util): |
|
115 | 115 | usr = user_util.create_user(password='qweqwe') |
|
116 | 116 | params = usr.get_api_data() # current user data |
|
117 | 117 | user_id = usr.user_id |
|
118 | 118 | self.log_user( |
|
119 | 119 | username=usr.username, password='qweqwe') |
|
120 | 120 | |
|
121 | 121 | params.update({'password_confirmation': ''}) |
|
122 | 122 | params.update({'new_password': ''}) |
|
123 | 123 | params.update({'extern_type': 'rhodecode'}) |
|
124 | 124 | params.update({'extern_name': 'rhodecode'}) |
|
125 | 125 | params.update({'csrf_token': self.csrf_token}) |
|
126 | 126 | |
|
127 | 127 | params.update(attrs) |
|
128 | 128 | # my account page cannot set language param yet, only for admins |
|
129 | 129 | del params['language'] |
|
130 | 130 | response = self.app.post(route_path('my_account_update'), params) |
|
131 | 131 | |
|
132 | 132 | assert_session_flash( |
|
133 | 133 | response, 'Your account was updated successfully') |
|
134 | 134 | |
|
135 | 135 | del params['csrf_token'] |
|
136 | 136 | |
|
137 | 137 | updated_user = User.get(user_id) |
|
138 | 138 | updated_params = updated_user.get_api_data() |
|
139 | 139 | updated_params.update({'password_confirmation': ''}) |
|
140 | 140 | updated_params.update({'new_password': ''}) |
|
141 | 141 | |
|
142 | 142 | params['last_login'] = updated_params['last_login'] |
|
143 | 143 | params['last_activity'] = updated_params['last_activity'] |
|
144 | 144 | # my account page cannot set language param yet, only for admins |
|
145 | 145 | # but we get this info from API anyway |
|
146 | 146 | params['language'] = updated_params['language'] |
|
147 | 147 | |
|
148 | 148 | if name == 'email': |
|
149 | 149 | params['emails'] = [attrs['email']] |
|
150 | 150 | if name == 'extern_type': |
|
151 | 151 | # cannot update this via form, expected value is original one |
|
152 | 152 | params['extern_type'] = "rhodecode" |
|
153 | 153 | if name == 'extern_name': |
|
154 | 154 | # cannot update this via form, expected value is original one |
|
155 | 155 | params['extern_name'] = str(user_id) |
|
156 | 156 | if name == 'active': |
|
157 | 157 | # my account cannot deactivate account |
|
158 | 158 | params['active'] = True |
|
159 | 159 | if name == 'admin': |
|
160 | 160 | # my account cannot make you an admin ! |
|
161 | 161 | params['admin'] = False |
|
162 | 162 | |
|
163 | 163 | assert params == updated_params |
|
164 | 164 | |
|
165 | 165 | def test_my_account_update_err_email_exists(self): |
|
166 | 166 | self.log_user() |
|
167 | 167 | |
|
168 | 168 | new_email = 'test_regular@mail.com' # already existing email |
|
169 | 169 | params = { |
|
170 | 170 | 'username': 'test_admin', |
|
171 | 171 | 'new_password': 'test12', |
|
172 | 172 | 'password_confirmation': 'test122', |
|
173 | 173 | 'firstname': 'NewName', |
|
174 | 174 | 'lastname': 'NewLastname', |
|
175 | 175 | 'email': new_email, |
|
176 | 176 | 'csrf_token': self.csrf_token, |
|
177 | 177 | } |
|
178 | 178 | |
|
179 | 179 | response = self.app.post(route_path('my_account_update'), |
|
180 | 180 | params=params) |
|
181 | 181 | |
|
182 | 182 | response.mustcontain('This e-mail address is already taken') |
|
183 | 183 | |
|
184 | 184 | def test_my_account_update_bad_email_address(self): |
|
185 | 185 | self.log_user('test_regular2', 'test12') |
|
186 | 186 | |
|
187 | 187 | new_email = 'newmail.pl' |
|
188 | 188 | params = { |
|
189 | 189 | 'username': 'test_admin', |
|
190 | 190 | 'new_password': 'test12', |
|
191 | 191 | 'password_confirmation': 'test122', |
|
192 | 192 | 'firstname': 'NewName', |
|
193 | 193 | 'lastname': 'NewLastname', |
|
194 | 194 | 'email': new_email, |
|
195 | 195 | 'csrf_token': self.csrf_token, |
|
196 | 196 | } |
|
197 | 197 | response = self.app.post(route_path('my_account_update'), |
|
198 | 198 | params=params) |
|
199 | 199 | |
|
200 | 200 | response.mustcontain('An email address must contain a single @') |
|
201 | from rhodecode.model import validators | |
|
202 | msg = validators.ValidUsername( | |
|
203 | edit=False, old_data={})._messages['username_exists'] | |
|
201 | msg = '???' | |
|
204 | 202 | msg = h.html_escape(msg % {'username': 'test_admin'}) |
|
205 | 203 | response.mustcontain(u"%s" % msg) |
@@ -1,580 +1,584 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | |
|
3 | 3 | # Copyright (C) 2016-2017 RhodeCode GmbH |
|
4 | 4 | # |
|
5 | 5 | # This program is free software: you can redistribute it and/or modify |
|
6 | 6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
7 | 7 | # (only), as published by the Free Software Foundation. |
|
8 | 8 | # |
|
9 | 9 | # This program is distributed in the hope that it will be useful, |
|
10 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 | 12 | # GNU General Public License for more details. |
|
13 | 13 | # |
|
14 | 14 | # You should have received a copy of the GNU Affero General Public License |
|
15 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | 16 | # |
|
17 | 17 | # This program is dual-licensed. If you wish to learn more about the |
|
18 | 18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
19 | 19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
20 | 20 | |
|
21 | 21 | import logging |
|
22 | 22 | import datetime |
|
23 | 23 | |
|
24 | 24 | import formencode |
|
25 | 25 | import formencode.htmlfill |
|
26 | 26 | from pyramid.httpexceptions import HTTPFound |
|
27 | 27 | from pyramid.view import view_config |
|
28 | 28 | from pyramid.renderers import render |
|
29 | 29 | from pyramid.response import Response |
|
30 | 30 | |
|
31 | 31 | from rhodecode.apps._base import BaseAppView, DataGridAppView |
|
32 | 32 | from rhodecode import forms |
|
33 | 33 | from rhodecode.lib import helpers as h |
|
34 | 34 | from rhodecode.lib import audit_logger |
|
35 | 35 | from rhodecode.lib.ext_json import json |
|
36 | 36 | from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired |
|
37 | 37 | from rhodecode.lib.channelstream import ( |
|
38 | 38 | channelstream_request, ChannelstreamException) |
|
39 | 39 | from rhodecode.lib.utils2 import safe_int, md5, str2bool |
|
40 | 40 | from rhodecode.model.auth_token import AuthTokenModel |
|
41 | 41 | from rhodecode.model.comment import CommentsModel |
|
42 | 42 | from rhodecode.model.db import ( |
|
43 | 43 | Repository, UserEmailMap, UserApiKeys, UserFollowing, joinedload, |
|
44 | 44 | PullRequest) |
|
45 | from rhodecode.model.forms import UserForm | |
|
45 | from rhodecode.model.forms import UserForm, UserExtraEmailForm | |
|
46 | 46 | from rhodecode.model.meta import Session |
|
47 | 47 | from rhodecode.model.pull_request import PullRequestModel |
|
48 | 48 | from rhodecode.model.scm import RepoList |
|
49 | 49 | from rhodecode.model.user import UserModel |
|
50 | 50 | from rhodecode.model.repo import RepoModel |
|
51 | 51 | from rhodecode.model.validation_schema.schemas import user_schema |
|
52 | 52 | |
|
53 | 53 | log = logging.getLogger(__name__) |
|
54 | 54 | |
|
55 | 55 | |
|
56 | 56 | class MyAccountView(BaseAppView, DataGridAppView): |
|
57 | 57 | ALLOW_SCOPED_TOKENS = False |
|
58 | 58 | """ |
|
59 | 59 | This view has alternative version inside EE, if modified please take a look |
|
60 | 60 | in there as well. |
|
61 | 61 | """ |
|
62 | 62 | |
|
63 | 63 | def load_default_context(self): |
|
64 | 64 | c = self._get_local_tmpl_context() |
|
65 | 65 | c.user = c.auth_user.get_instance() |
|
66 | 66 | c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS |
|
67 | self._register_global_c(c) | |
|
67 | ||
|
68 | 68 | return c |
|
69 | 69 | |
|
70 | 70 | @LoginRequired() |
|
71 | 71 | @NotAnonymous() |
|
72 | 72 | @view_config( |
|
73 | 73 | route_name='my_account_profile', request_method='GET', |
|
74 | 74 | renderer='rhodecode:templates/admin/my_account/my_account.mako') |
|
75 | 75 | def my_account_profile(self): |
|
76 | 76 | c = self.load_default_context() |
|
77 | 77 | c.active = 'profile' |
|
78 | 78 | return self._get_template_context(c) |
|
79 | 79 | |
|
80 | 80 | @LoginRequired() |
|
81 | 81 | @NotAnonymous() |
|
82 | 82 | @view_config( |
|
83 | 83 | route_name='my_account_password', request_method='GET', |
|
84 | 84 | renderer='rhodecode:templates/admin/my_account/my_account.mako') |
|
85 | 85 | def my_account_password(self): |
|
86 | 86 | c = self.load_default_context() |
|
87 | 87 | c.active = 'password' |
|
88 | 88 | c.extern_type = c.user.extern_type |
|
89 | 89 | |
|
90 | 90 | schema = user_schema.ChangePasswordSchema().bind( |
|
91 | 91 | username=c.user.username) |
|
92 | 92 | |
|
93 | 93 | form = forms.Form( |
|
94 | 94 | schema, |
|
95 | 95 | action=h.route_path('my_account_password_update'), |
|
96 | 96 | buttons=(forms.buttons.save, forms.buttons.reset)) |
|
97 | 97 | |
|
98 | 98 | c.form = form |
|
99 | 99 | return self._get_template_context(c) |
|
100 | 100 | |
|
101 | 101 | @LoginRequired() |
|
102 | 102 | @NotAnonymous() |
|
103 | 103 | @CSRFRequired() |
|
104 | 104 | @view_config( |
|
105 | 105 | route_name='my_account_password_update', request_method='POST', |
|
106 | 106 | renderer='rhodecode:templates/admin/my_account/my_account.mako') |
|
107 | 107 | def my_account_password_update(self): |
|
108 | 108 | _ = self.request.translate |
|
109 | 109 | c = self.load_default_context() |
|
110 | 110 | c.active = 'password' |
|
111 | 111 | c.extern_type = c.user.extern_type |
|
112 | 112 | |
|
113 | 113 | schema = user_schema.ChangePasswordSchema().bind( |
|
114 | 114 | username=c.user.username) |
|
115 | 115 | |
|
116 | 116 | form = forms.Form( |
|
117 | 117 | schema, buttons=(forms.buttons.save, forms.buttons.reset)) |
|
118 | 118 | |
|
119 | 119 | if c.extern_type != 'rhodecode': |
|
120 | 120 | raise HTTPFound(self.request.route_path('my_account_password')) |
|
121 | 121 | |
|
122 | 122 | controls = self.request.POST.items() |
|
123 | 123 | try: |
|
124 | 124 | valid_data = form.validate(controls) |
|
125 | 125 | UserModel().update_user(c.user.user_id, **valid_data) |
|
126 | 126 | c.user.update_userdata(force_password_change=False) |
|
127 | 127 | Session().commit() |
|
128 | 128 | except forms.ValidationFailure as e: |
|
129 | 129 | c.form = e |
|
130 | 130 | return self._get_template_context(c) |
|
131 | 131 | |
|
132 | 132 | except Exception: |
|
133 | 133 | log.exception("Exception updating password") |
|
134 | 134 | h.flash(_('Error occurred during update of user password'), |
|
135 | 135 | category='error') |
|
136 | 136 | else: |
|
137 | 137 | instance = c.auth_user.get_instance() |
|
138 | 138 | self.session.setdefault('rhodecode_user', {}).update( |
|
139 | 139 | {'password': md5(instance.password)}) |
|
140 | 140 | self.session.save() |
|
141 | 141 | h.flash(_("Successfully updated password"), category='success') |
|
142 | 142 | |
|
143 | 143 | raise HTTPFound(self.request.route_path('my_account_password')) |
|
144 | 144 | |
|
145 | 145 | @LoginRequired() |
|
146 | 146 | @NotAnonymous() |
|
147 | 147 | @view_config( |
|
148 | 148 | route_name='my_account_auth_tokens', request_method='GET', |
|
149 | 149 | renderer='rhodecode:templates/admin/my_account/my_account.mako') |
|
150 | 150 | def my_account_auth_tokens(self): |
|
151 | 151 | _ = self.request.translate |
|
152 | 152 | |
|
153 | 153 | c = self.load_default_context() |
|
154 | 154 | c.active = 'auth_tokens' |
|
155 | 155 | c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_) |
|
156 | 156 | c.role_values = [ |
|
157 | 157 | (x, AuthTokenModel.cls._get_role_name(x)) |
|
158 | 158 | for x in AuthTokenModel.cls.ROLES] |
|
159 | 159 | c.role_options = [(c.role_values, _("Role"))] |
|
160 | 160 | c.user_auth_tokens = AuthTokenModel().get_auth_tokens( |
|
161 | 161 | c.user.user_id, show_expired=True) |
|
162 | 162 | c.role_vcs = AuthTokenModel.cls.ROLE_VCS |
|
163 | 163 | return self._get_template_context(c) |
|
164 | 164 | |
|
165 | 165 | def maybe_attach_token_scope(self, token): |
|
166 | 166 | # implemented in EE edition |
|
167 | 167 | pass |
|
168 | 168 | |
|
169 | 169 | @LoginRequired() |
|
170 | 170 | @NotAnonymous() |
|
171 | 171 | @CSRFRequired() |
|
172 | 172 | @view_config( |
|
173 | 173 | route_name='my_account_auth_tokens_add', request_method='POST',) |
|
174 | 174 | def my_account_auth_tokens_add(self): |
|
175 | 175 | _ = self.request.translate |
|
176 | 176 | c = self.load_default_context() |
|
177 | 177 | |
|
178 | 178 | lifetime = safe_int(self.request.POST.get('lifetime'), -1) |
|
179 | 179 | description = self.request.POST.get('description') |
|
180 | 180 | role = self.request.POST.get('role') |
|
181 | 181 | |
|
182 | 182 | token = AuthTokenModel().create( |
|
183 | 183 | c.user.user_id, description, lifetime, role) |
|
184 | 184 | token_data = token.get_api_data() |
|
185 | 185 | |
|
186 | 186 | self.maybe_attach_token_scope(token) |
|
187 | 187 | audit_logger.store_web( |
|
188 | 188 | 'user.edit.token.add', action_data={ |
|
189 | 189 | 'data': {'token': token_data, 'user': 'self'}}, |
|
190 | 190 | user=self._rhodecode_user, ) |
|
191 | 191 | Session().commit() |
|
192 | 192 | |
|
193 | 193 | h.flash(_("Auth token successfully created"), category='success') |
|
194 | 194 | return HTTPFound(h.route_path('my_account_auth_tokens')) |
|
195 | 195 | |
|
196 | 196 | @LoginRequired() |
|
197 | 197 | @NotAnonymous() |
|
198 | 198 | @CSRFRequired() |
|
199 | 199 | @view_config( |
|
200 | 200 | route_name='my_account_auth_tokens_delete', request_method='POST') |
|
201 | 201 | def my_account_auth_tokens_delete(self): |
|
202 | 202 | _ = self.request.translate |
|
203 | 203 | c = self.load_default_context() |
|
204 | 204 | |
|
205 | 205 | del_auth_token = self.request.POST.get('del_auth_token') |
|
206 | 206 | |
|
207 | 207 | if del_auth_token: |
|
208 | 208 | token = UserApiKeys.get_or_404(del_auth_token) |
|
209 | 209 | token_data = token.get_api_data() |
|
210 | 210 | |
|
211 | 211 | AuthTokenModel().delete(del_auth_token, c.user.user_id) |
|
212 | 212 | audit_logger.store_web( |
|
213 | 213 | 'user.edit.token.delete', action_data={ |
|
214 | 214 | 'data': {'token': token_data, 'user': 'self'}}, |
|
215 | 215 | user=self._rhodecode_user,) |
|
216 | 216 | Session().commit() |
|
217 | 217 | h.flash(_("Auth token successfully deleted"), category='success') |
|
218 | 218 | |
|
219 | 219 | return HTTPFound(h.route_path('my_account_auth_tokens')) |
|
220 | 220 | |
|
221 | 221 | @LoginRequired() |
|
222 | 222 | @NotAnonymous() |
|
223 | 223 | @view_config( |
|
224 | 224 | route_name='my_account_emails', request_method='GET', |
|
225 | 225 | renderer='rhodecode:templates/admin/my_account/my_account.mako') |
|
226 | 226 | def my_account_emails(self): |
|
227 | 227 | _ = self.request.translate |
|
228 | 228 | |
|
229 | 229 | c = self.load_default_context() |
|
230 | 230 | c.active = 'emails' |
|
231 | 231 | |
|
232 | 232 | c.user_email_map = UserEmailMap.query()\ |
|
233 | 233 | .filter(UserEmailMap.user == c.user).all() |
|
234 | 234 | return self._get_template_context(c) |
|
235 | 235 | |
|
236 | 236 | @LoginRequired() |
|
237 | 237 | @NotAnonymous() |
|
238 | 238 | @CSRFRequired() |
|
239 | 239 | @view_config( |
|
240 | 240 | route_name='my_account_emails_add', request_method='POST') |
|
241 | 241 | def my_account_emails_add(self): |
|
242 | 242 | _ = self.request.translate |
|
243 | 243 | c = self.load_default_context() |
|
244 | 244 | |
|
245 | 245 | email = self.request.POST.get('new_email') |
|
246 | 246 | |
|
247 | 247 | try: |
|
248 | form = UserExtraEmailForm(self.request.translate)() | |
|
249 | data = form.to_python({'email': email}) | |
|
250 | email = data['email'] | |
|
251 | ||
|
248 | 252 | UserModel().add_extra_email(c.user.user_id, email) |
|
249 | 253 | audit_logger.store_web( |
|
250 | 254 | 'user.edit.email.add', action_data={ |
|
251 | 255 | 'data': {'email': email, 'user': 'self'}}, |
|
252 | 256 | user=self._rhodecode_user,) |
|
253 | 257 | |
|
254 | 258 | Session().commit() |
|
255 | 259 | h.flash(_("Added new email address `%s` for user account") % email, |
|
256 | 260 | category='success') |
|
257 | 261 | except formencode.Invalid as error: |
|
258 | 262 | h.flash(h.escape(error.error_dict['email']), category='error') |
|
259 | 263 | except Exception: |
|
260 | 264 | log.exception("Exception in my_account_emails") |
|
261 | 265 | h.flash(_('An error occurred during email saving'), |
|
262 | 266 | category='error') |
|
263 | 267 | return HTTPFound(h.route_path('my_account_emails')) |
|
264 | 268 | |
|
265 | 269 | @LoginRequired() |
|
266 | 270 | @NotAnonymous() |
|
267 | 271 | @CSRFRequired() |
|
268 | 272 | @view_config( |
|
269 | 273 | route_name='my_account_emails_delete', request_method='POST') |
|
270 | 274 | def my_account_emails_delete(self): |
|
271 | 275 | _ = self.request.translate |
|
272 | 276 | c = self.load_default_context() |
|
273 | 277 | |
|
274 | 278 | del_email_id = self.request.POST.get('del_email_id') |
|
275 | 279 | if del_email_id: |
|
276 | 280 | email = UserEmailMap.get_or_404(del_email_id).email |
|
277 | 281 | UserModel().delete_extra_email(c.user.user_id, del_email_id) |
|
278 | 282 | audit_logger.store_web( |
|
279 | 283 | 'user.edit.email.delete', action_data={ |
|
280 | 284 | 'data': {'email': email, 'user': 'self'}}, |
|
281 | 285 | user=self._rhodecode_user,) |
|
282 | 286 | Session().commit() |
|
283 | 287 | h.flash(_("Email successfully deleted"), |
|
284 | 288 | category='success') |
|
285 | 289 | return HTTPFound(h.route_path('my_account_emails')) |
|
286 | 290 | |
|
287 | 291 | @LoginRequired() |
|
288 | 292 | @NotAnonymous() |
|
289 | 293 | @CSRFRequired() |
|
290 | 294 | @view_config( |
|
291 | 295 | route_name='my_account_notifications_test_channelstream', |
|
292 | 296 | request_method='POST', renderer='json_ext') |
|
293 | 297 | def my_account_notifications_test_channelstream(self): |
|
294 | 298 | message = 'Test message sent via Channelstream by user: {}, on {}'.format( |
|
295 | 299 | self._rhodecode_user.username, datetime.datetime.now()) |
|
296 | 300 | payload = { |
|
297 | 301 | # 'channel': 'broadcast', |
|
298 | 302 | 'type': 'message', |
|
299 | 303 | 'timestamp': datetime.datetime.utcnow(), |
|
300 | 304 | 'user': 'system', |
|
301 | 305 | 'pm_users': [self._rhodecode_user.username], |
|
302 | 306 | 'message': { |
|
303 | 307 | 'message': message, |
|
304 | 308 | 'level': 'info', |
|
305 | 309 | 'topic': '/notifications' |
|
306 | 310 | } |
|
307 | 311 | } |
|
308 | 312 | |
|
309 | 313 | registry = self.request.registry |
|
310 | 314 | rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {}) |
|
311 | 315 | channelstream_config = rhodecode_plugins.get('channelstream', {}) |
|
312 | 316 | |
|
313 | 317 | try: |
|
314 | 318 | channelstream_request(channelstream_config, [payload], '/message') |
|
315 | 319 | except ChannelstreamException as e: |
|
316 | 320 | log.exception('Failed to send channelstream data') |
|
317 | 321 | return {"response": 'ERROR: {}'.format(e.__class__.__name__)} |
|
318 | 322 | return {"response": 'Channelstream data sent. ' |
|
319 | 323 | 'You should see a new live message now.'} |
|
320 | 324 | |
|
321 | 325 | def _load_my_repos_data(self, watched=False): |
|
322 | 326 | if watched: |
|
323 | 327 | admin = False |
|
324 | 328 | follows_repos = Session().query(UserFollowing)\ |
|
325 | 329 | .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\ |
|
326 | 330 | .options(joinedload(UserFollowing.follows_repository))\ |
|
327 | 331 | .all() |
|
328 | 332 | repo_list = [x.follows_repository for x in follows_repos] |
|
329 | 333 | else: |
|
330 | 334 | admin = True |
|
331 | 335 | repo_list = Repository.get_all_repos( |
|
332 | 336 | user_id=self._rhodecode_user.user_id) |
|
333 | 337 | repo_list = RepoList(repo_list, perm_set=[ |
|
334 | 338 | 'repository.read', 'repository.write', 'repository.admin']) |
|
335 | 339 | |
|
336 | 340 | repos_data = RepoModel().get_repos_as_dict( |
|
337 | 341 | repo_list=repo_list, admin=admin) |
|
338 | 342 | # json used to render the grid |
|
339 | 343 | return json.dumps(repos_data) |
|
340 | 344 | |
|
341 | 345 | @LoginRequired() |
|
342 | 346 | @NotAnonymous() |
|
343 | 347 | @view_config( |
|
344 | 348 | route_name='my_account_repos', request_method='GET', |
|
345 | 349 | renderer='rhodecode:templates/admin/my_account/my_account.mako') |
|
346 | 350 | def my_account_repos(self): |
|
347 | 351 | c = self.load_default_context() |
|
348 | 352 | c.active = 'repos' |
|
349 | 353 | |
|
350 | 354 | # json used to render the grid |
|
351 | 355 | c.data = self._load_my_repos_data() |
|
352 | 356 | return self._get_template_context(c) |
|
353 | 357 | |
|
354 | 358 | @LoginRequired() |
|
355 | 359 | @NotAnonymous() |
|
356 | 360 | @view_config( |
|
357 | 361 | route_name='my_account_watched', request_method='GET', |
|
358 | 362 | renderer='rhodecode:templates/admin/my_account/my_account.mako') |
|
359 | 363 | def my_account_watched(self): |
|
360 | 364 | c = self.load_default_context() |
|
361 | 365 | c.active = 'watched' |
|
362 | 366 | |
|
363 | 367 | # json used to render the grid |
|
364 | 368 | c.data = self._load_my_repos_data(watched=True) |
|
365 | 369 | return self._get_template_context(c) |
|
366 | 370 | |
|
367 | 371 | @LoginRequired() |
|
368 | 372 | @NotAnonymous() |
|
369 | 373 | @view_config( |
|
370 | 374 | route_name='my_account_perms', request_method='GET', |
|
371 | 375 | renderer='rhodecode:templates/admin/my_account/my_account.mako') |
|
372 | 376 | def my_account_perms(self): |
|
373 | 377 | c = self.load_default_context() |
|
374 | 378 | c.active = 'perms' |
|
375 | 379 | |
|
376 | 380 | c.perm_user = c.auth_user |
|
377 | 381 | return self._get_template_context(c) |
|
378 | 382 | |
|
379 | 383 | @LoginRequired() |
|
380 | 384 | @NotAnonymous() |
|
381 | 385 | @view_config( |
|
382 | 386 | route_name='my_account_notifications', request_method='GET', |
|
383 | 387 | renderer='rhodecode:templates/admin/my_account/my_account.mako') |
|
384 | 388 | def my_notifications(self): |
|
385 | 389 | c = self.load_default_context() |
|
386 | 390 | c.active = 'notifications' |
|
387 | 391 | |
|
388 | 392 | return self._get_template_context(c) |
|
389 | 393 | |
|
390 | 394 | @LoginRequired() |
|
391 | 395 | @NotAnonymous() |
|
392 | 396 | @CSRFRequired() |
|
393 | 397 | @view_config( |
|
394 | 398 | route_name='my_account_notifications_toggle_visibility', |
|
395 | 399 | request_method='POST', renderer='json_ext') |
|
396 | 400 | def my_notifications_toggle_visibility(self): |
|
397 | 401 | user = self._rhodecode_db_user |
|
398 | 402 | new_status = not user.user_data.get('notification_status', True) |
|
399 | 403 | user.update_userdata(notification_status=new_status) |
|
400 | 404 | Session().commit() |
|
401 | 405 | return user.user_data['notification_status'] |
|
402 | 406 | |
|
403 | 407 | @LoginRequired() |
|
404 | 408 | @NotAnonymous() |
|
405 | 409 | @view_config( |
|
406 | 410 | route_name='my_account_edit', |
|
407 | 411 | request_method='GET', |
|
408 | 412 | renderer='rhodecode:templates/admin/my_account/my_account.mako') |
|
409 | 413 | def my_account_edit(self): |
|
410 | 414 | c = self.load_default_context() |
|
411 | 415 | c.active = 'profile_edit' |
|
412 | 416 | |
|
413 | 417 | c.perm_user = c.auth_user |
|
414 | 418 | c.extern_type = c.user.extern_type |
|
415 | 419 | c.extern_name = c.user.extern_name |
|
416 | 420 | |
|
417 | 421 | defaults = c.user.get_dict() |
|
418 | 422 | |
|
419 | 423 | data = render('rhodecode:templates/admin/my_account/my_account.mako', |
|
420 | 424 | self._get_template_context(c), self.request) |
|
421 | 425 | html = formencode.htmlfill.render( |
|
422 | 426 | data, |
|
423 | 427 | defaults=defaults, |
|
424 | 428 | encoding="UTF-8", |
|
425 | 429 | force_defaults=False |
|
426 | 430 | ) |
|
427 | 431 | return Response(html) |
|
428 | 432 | |
|
429 | 433 | @LoginRequired() |
|
430 | 434 | @NotAnonymous() |
|
431 | 435 | @CSRFRequired() |
|
432 | 436 | @view_config( |
|
433 | 437 | route_name='my_account_update', |
|
434 | 438 | request_method='POST', |
|
435 | 439 | renderer='rhodecode:templates/admin/my_account/my_account.mako') |
|
436 | 440 | def my_account_update(self): |
|
437 | 441 | _ = self.request.translate |
|
438 | 442 | c = self.load_default_context() |
|
439 | 443 | c.active = 'profile_edit' |
|
440 | 444 | |
|
441 | 445 | c.perm_user = c.auth_user |
|
442 | 446 | c.extern_type = c.user.extern_type |
|
443 | 447 | c.extern_name = c.user.extern_name |
|
444 | 448 | |
|
445 | _form = UserForm(edit=True, | |
|
449 | _form = UserForm(self.request.translate, edit=True, | |
|
446 | 450 | old_data={'user_id': self._rhodecode_user.user_id, |
|
447 | 451 | 'email': self._rhodecode_user.email})() |
|
448 | 452 | form_result = {} |
|
449 | 453 | try: |
|
450 | 454 | post_data = dict(self.request.POST) |
|
451 | 455 | post_data['new_password'] = '' |
|
452 | 456 | post_data['password_confirmation'] = '' |
|
453 | 457 | form_result = _form.to_python(post_data) |
|
454 | 458 | # skip updating those attrs for my account |
|
455 | 459 | skip_attrs = ['admin', 'active', 'extern_type', 'extern_name', |
|
456 | 460 | 'new_password', 'password_confirmation'] |
|
457 | 461 | # TODO: plugin should define if username can be updated |
|
458 | 462 | if c.extern_type != "rhodecode": |
|
459 | 463 | # forbid updating username for external accounts |
|
460 | 464 | skip_attrs.append('username') |
|
461 | 465 | |
|
462 | 466 | UserModel().update_user( |
|
463 | 467 | self._rhodecode_user.user_id, skip_attrs=skip_attrs, |
|
464 | 468 | **form_result) |
|
465 | 469 | h.flash(_('Your account was updated successfully'), |
|
466 | 470 | category='success') |
|
467 | 471 | Session().commit() |
|
468 | 472 | |
|
469 | 473 | except formencode.Invalid as errors: |
|
470 | 474 | data = render( |
|
471 | 475 | 'rhodecode:templates/admin/my_account/my_account.mako', |
|
472 | 476 | self._get_template_context(c), self.request) |
|
473 | 477 | |
|
474 | 478 | html = formencode.htmlfill.render( |
|
475 | 479 | data, |
|
476 | 480 | defaults=errors.value, |
|
477 | 481 | errors=errors.error_dict or {}, |
|
478 | 482 | prefix_error=False, |
|
479 | 483 | encoding="UTF-8", |
|
480 | 484 | force_defaults=False) |
|
481 | 485 | return Response(html) |
|
482 | 486 | |
|
483 | 487 | except Exception: |
|
484 | 488 | log.exception("Exception updating user") |
|
485 | 489 | h.flash(_('Error occurred during update of user %s') |
|
486 | 490 | % form_result.get('username'), category='error') |
|
487 | 491 | raise HTTPFound(h.route_path('my_account_profile')) |
|
488 | 492 | |
|
489 | 493 | raise HTTPFound(h.route_path('my_account_profile')) |
|
490 | 494 | |
|
491 | 495 | def _get_pull_requests_list(self, statuses): |
|
492 | 496 | draw, start, limit = self._extract_chunk(self.request) |
|
493 | 497 | search_q, order_by, order_dir = self._extract_ordering(self.request) |
|
494 | 498 | _render = self.request.get_partial_renderer( |
|
495 | 499 | 'rhodecode:templates/data_table/_dt_elements.mako') |
|
496 | 500 | |
|
497 | 501 | pull_requests = PullRequestModel().get_im_participating_in( |
|
498 | 502 | user_id=self._rhodecode_user.user_id, |
|
499 | 503 | statuses=statuses, |
|
500 | 504 | offset=start, length=limit, order_by=order_by, |
|
501 | 505 | order_dir=order_dir) |
|
502 | 506 | |
|
503 | 507 | pull_requests_total_count = PullRequestModel().count_im_participating_in( |
|
504 | 508 | user_id=self._rhodecode_user.user_id, statuses=statuses) |
|
505 | 509 | |
|
506 | 510 | data = [] |
|
507 | 511 | comments_model = CommentsModel() |
|
508 | 512 | for pr in pull_requests: |
|
509 | 513 | repo_id = pr.target_repo_id |
|
510 | 514 | comments = comments_model.get_all_comments( |
|
511 | 515 | repo_id, pull_request=pr) |
|
512 | 516 | owned = pr.user_id == self._rhodecode_user.user_id |
|
513 | 517 | |
|
514 | 518 | data.append({ |
|
515 | 519 | 'target_repo': _render('pullrequest_target_repo', |
|
516 | 520 | pr.target_repo.repo_name), |
|
517 | 521 | 'name': _render('pullrequest_name', |
|
518 | 522 | pr.pull_request_id, pr.target_repo.repo_name, |
|
519 | 523 | short=True), |
|
520 | 524 | 'name_raw': pr.pull_request_id, |
|
521 | 525 | 'status': _render('pullrequest_status', |
|
522 | 526 | pr.calculated_review_status()), |
|
523 | 527 | 'title': _render( |
|
524 | 528 | 'pullrequest_title', pr.title, pr.description), |
|
525 | 529 | 'description': h.escape(pr.description), |
|
526 | 530 | 'updated_on': _render('pullrequest_updated_on', |
|
527 | 531 | h.datetime_to_time(pr.updated_on)), |
|
528 | 532 | 'updated_on_raw': h.datetime_to_time(pr.updated_on), |
|
529 | 533 | 'created_on': _render('pullrequest_updated_on', |
|
530 | 534 | h.datetime_to_time(pr.created_on)), |
|
531 | 535 | 'created_on_raw': h.datetime_to_time(pr.created_on), |
|
532 | 536 | 'author': _render('pullrequest_author', |
|
533 | 537 | pr.author.full_contact, ), |
|
534 | 538 | 'author_raw': pr.author.full_name, |
|
535 | 539 | 'comments': _render('pullrequest_comments', len(comments)), |
|
536 | 540 | 'comments_raw': len(comments), |
|
537 | 541 | 'closed': pr.is_closed(), |
|
538 | 542 | 'owned': owned |
|
539 | 543 | }) |
|
540 | 544 | |
|
541 | 545 | # json used to render the grid |
|
542 | 546 | data = ({ |
|
543 | 547 | 'draw': draw, |
|
544 | 548 | 'data': data, |
|
545 | 549 | 'recordsTotal': pull_requests_total_count, |
|
546 | 550 | 'recordsFiltered': pull_requests_total_count, |
|
547 | 551 | }) |
|
548 | 552 | return data |
|
549 | 553 | |
|
550 | 554 | @LoginRequired() |
|
551 | 555 | @NotAnonymous() |
|
552 | 556 | @view_config( |
|
553 | 557 | route_name='my_account_pullrequests', |
|
554 | 558 | request_method='GET', |
|
555 | 559 | renderer='rhodecode:templates/admin/my_account/my_account.mako') |
|
556 | 560 | def my_account_pullrequests(self): |
|
557 | 561 | c = self.load_default_context() |
|
558 | 562 | c.active = 'pullrequests' |
|
559 | 563 | req_get = self.request.GET |
|
560 | 564 | |
|
561 | 565 | c.closed = str2bool(req_get.get('pr_show_closed')) |
|
562 | 566 | |
|
563 | 567 | return self._get_template_context(c) |
|
564 | 568 | |
|
565 | 569 | @LoginRequired() |
|
566 | 570 | @NotAnonymous() |
|
567 | 571 | @view_config( |
|
568 | 572 | route_name='my_account_pullrequests_data', |
|
569 | 573 | request_method='GET', renderer='json_ext') |
|
570 | 574 | def my_account_pullrequests_data(self): |
|
571 | 575 | req_get = self.request.GET |
|
572 | 576 | closed = str2bool(req_get.get('closed')) |
|
573 | 577 | |
|
574 | 578 | statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN] |
|
575 | 579 | if closed: |
|
576 | 580 | statuses += [PullRequest.STATUS_CLOSED] |
|
577 | 581 | |
|
578 | 582 | data = self._get_pull_requests_list(statuses=statuses) |
|
579 | 583 | return data |
|
580 | 584 |
@@ -1,198 +1,198 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | |
|
3 | 3 | # Copyright (C) 2010-2017 RhodeCode GmbH |
|
4 | 4 | # |
|
5 | 5 | # This program is free software: you can redistribute it and/or modify |
|
6 | 6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
7 | 7 | # (only), as published by the Free Software Foundation. |
|
8 | 8 | # |
|
9 | 9 | # This program is distributed in the hope that it will be useful, |
|
10 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 | 12 | # GNU General Public License for more details. |
|
13 | 13 | # |
|
14 | 14 | # You should have received a copy of the GNU Affero General Public License |
|
15 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | 16 | # |
|
17 | 17 | # This program is dual-licensed. If you wish to learn more about the |
|
18 | 18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
19 | 19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
20 | 20 | |
|
21 | 21 | import logging |
|
22 | 22 | |
|
23 | 23 | from pyramid.httpexceptions import ( |
|
24 | 24 | HTTPFound, HTTPNotFound, HTTPInternalServerError) |
|
25 | 25 | from pyramid.view import view_config |
|
26 | 26 | |
|
27 | 27 | from rhodecode.apps._base import BaseAppView |
|
28 | 28 | from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired |
|
29 | 29 | |
|
30 | 30 | from rhodecode.lib import helpers as h |
|
31 | 31 | from rhodecode.lib.helpers import Page |
|
32 | 32 | from rhodecode.lib.utils2 import safe_int |
|
33 | 33 | from rhodecode.model.db import Notification |
|
34 | 34 | from rhodecode.model.notification import NotificationModel |
|
35 | 35 | from rhodecode.model.meta import Session |
|
36 | 36 | |
|
37 | 37 | |
|
38 | 38 | log = logging.getLogger(__name__) |
|
39 | 39 | |
|
40 | 40 | |
|
41 | 41 | class MyAccountNotificationsView(BaseAppView): |
|
42 | 42 | |
|
43 | 43 | def load_default_context(self): |
|
44 | 44 | c = self._get_local_tmpl_context() |
|
45 | 45 | c.user = c.auth_user.get_instance() |
|
46 | self._register_global_c(c) | |
|
46 | ||
|
47 | 47 | return c |
|
48 | 48 | |
|
49 | 49 | def _has_permissions(self, notification): |
|
50 | 50 | def is_owner(): |
|
51 | 51 | user_id = self._rhodecode_db_user.user_id |
|
52 | 52 | for user_notification in notification.notifications_to_users: |
|
53 | 53 | if user_notification.user.user_id == user_id: |
|
54 | 54 | return True |
|
55 | 55 | return False |
|
56 | 56 | return h.HasPermissionAny('hg.admin')() or is_owner() |
|
57 | 57 | |
|
58 | 58 | @LoginRequired() |
|
59 | 59 | @NotAnonymous() |
|
60 | 60 | @view_config( |
|
61 | 61 | route_name='notifications_show_all', request_method='GET', |
|
62 | 62 | renderer='rhodecode:templates/admin/notifications/notifications_show_all.mako') |
|
63 | 63 | def notifications_show_all(self): |
|
64 | 64 | c = self.load_default_context() |
|
65 | 65 | |
|
66 | 66 | c.unread_count = NotificationModel().get_unread_cnt_for_user( |
|
67 | 67 | self._rhodecode_db_user.user_id) |
|
68 | 68 | |
|
69 | 69 | _current_filter = self.request.GET.getall('type') or ['unread'] |
|
70 | 70 | |
|
71 | 71 | notifications = NotificationModel().get_for_user( |
|
72 | 72 | self._rhodecode_db_user.user_id, |
|
73 | 73 | filter_=_current_filter) |
|
74 | 74 | |
|
75 | 75 | p = safe_int(self.request.GET.get('page', 1), 1) |
|
76 | 76 | |
|
77 | 77 | def url_generator(**kw): |
|
78 | 78 | _query = self.request.GET.mixed() |
|
79 | 79 | _query.update(kw) |
|
80 | 80 | return self.request.current_route_path(_query=_query) |
|
81 | 81 | |
|
82 | 82 | c.notifications = Page(notifications, page=p, items_per_page=10, |
|
83 | 83 | url=url_generator) |
|
84 | 84 | |
|
85 | 85 | c.unread_type = 'unread' |
|
86 | 86 | c.all_type = 'all' |
|
87 | 87 | c.pull_request_type = Notification.TYPE_PULL_REQUEST |
|
88 | 88 | c.comment_type = [Notification.TYPE_CHANGESET_COMMENT, |
|
89 | 89 | Notification.TYPE_PULL_REQUEST_COMMENT] |
|
90 | 90 | |
|
91 | 91 | c.current_filter = 'unread' # default filter |
|
92 | 92 | |
|
93 | 93 | if _current_filter == [c.pull_request_type]: |
|
94 | 94 | c.current_filter = 'pull_request' |
|
95 | 95 | elif _current_filter == c.comment_type: |
|
96 | 96 | c.current_filter = 'comment' |
|
97 | 97 | elif _current_filter == [c.unread_type]: |
|
98 | 98 | c.current_filter = 'unread' |
|
99 | 99 | elif _current_filter == [c.all_type]: |
|
100 | 100 | c.current_filter = 'all' |
|
101 | 101 | return self._get_template_context(c) |
|
102 | 102 | |
|
103 | 103 | @LoginRequired() |
|
104 | 104 | @NotAnonymous() |
|
105 | 105 | @CSRFRequired() |
|
106 | 106 | @view_config( |
|
107 | 107 | route_name='notifications_mark_all_read', request_method='POST', |
|
108 | 108 | renderer='rhodecode:templates/admin/notifications/notifications_show_all.mako') |
|
109 | 109 | def notifications_mark_all_read(self): |
|
110 | 110 | NotificationModel().mark_all_read_for_user( |
|
111 | 111 | self._rhodecode_db_user.user_id, |
|
112 | 112 | filter_=self.request.GET.getall('type')) |
|
113 | 113 | Session().commit() |
|
114 | 114 | raise HTTPFound(h.route_path('notifications_show_all')) |
|
115 | 115 | |
|
116 | 116 | @LoginRequired() |
|
117 | 117 | @NotAnonymous() |
|
118 | 118 | @view_config( |
|
119 | 119 | route_name='notifications_show', request_method='GET', |
|
120 | 120 | renderer='rhodecode:templates/admin/notifications/notifications_show.mako') |
|
121 | 121 | def notifications_show(self): |
|
122 | 122 | c = self.load_default_context() |
|
123 | 123 | notification_id = self.request.matchdict['notification_id'] |
|
124 | 124 | notification = Notification.get_or_404(notification_id) |
|
125 | 125 | |
|
126 | 126 | if not self._has_permissions(notification): |
|
127 | 127 | log.debug('User %s does not have permission to access notification', |
|
128 | 128 | self._rhodecode_user) |
|
129 | 129 | raise HTTPNotFound() |
|
130 | 130 | |
|
131 | 131 | u_notification = NotificationModel().get_user_notification( |
|
132 | 132 | self._rhodecode_db_user.user_id, notification) |
|
133 | 133 | if not u_notification: |
|
134 | 134 | log.debug('User %s notification does not exist', |
|
135 | 135 | self._rhodecode_user) |
|
136 | 136 | raise HTTPNotFound() |
|
137 | 137 | |
|
138 | 138 | # when opening this notification, mark it as read for this use |
|
139 | 139 | if not u_notification.read: |
|
140 | 140 | u_notification.mark_as_read() |
|
141 | 141 | Session().commit() |
|
142 | 142 | |
|
143 | 143 | c.notification = notification |
|
144 | 144 | |
|
145 | 145 | return self._get_template_context(c) |
|
146 | 146 | |
|
147 | 147 | @LoginRequired() |
|
148 | 148 | @NotAnonymous() |
|
149 | 149 | @CSRFRequired() |
|
150 | 150 | @view_config( |
|
151 | 151 | route_name='notifications_update', request_method='POST', |
|
152 | 152 | renderer='json_ext') |
|
153 | 153 | def notification_update(self): |
|
154 | 154 | notification_id = self.request.matchdict['notification_id'] |
|
155 | 155 | notification = Notification.get_or_404(notification_id) |
|
156 | 156 | |
|
157 | 157 | if not self._has_permissions(notification): |
|
158 | 158 | log.debug('User %s does not have permission to access notification', |
|
159 | 159 | self._rhodecode_user) |
|
160 | 160 | raise HTTPNotFound() |
|
161 | 161 | |
|
162 | 162 | try: |
|
163 | 163 | # updates notification read flag |
|
164 | 164 | NotificationModel().mark_read( |
|
165 | 165 | self._rhodecode_user.user_id, notification) |
|
166 | 166 | Session().commit() |
|
167 | 167 | return 'ok' |
|
168 | 168 | except Exception: |
|
169 | 169 | Session().rollback() |
|
170 | 170 | log.exception("Exception updating a notification item") |
|
171 | 171 | |
|
172 | 172 | raise HTTPInternalServerError() |
|
173 | 173 | |
|
174 | 174 | @LoginRequired() |
|
175 | 175 | @NotAnonymous() |
|
176 | 176 | @CSRFRequired() |
|
177 | 177 | @view_config( |
|
178 | 178 | route_name='notifications_delete', request_method='POST', |
|
179 | 179 | renderer='json_ext') |
|
180 | 180 | def notification_delete(self): |
|
181 | 181 | notification_id = self.request.matchdict['notification_id'] |
|
182 | 182 | notification = Notification.get_or_404(notification_id) |
|
183 | 183 | if not self._has_permissions(notification): |
|
184 | 184 | log.debug('User %s does not have permission to access notification', |
|
185 | 185 | self._rhodecode_user) |
|
186 | 186 | raise HTTPNotFound() |
|
187 | 187 | |
|
188 | 188 | try: |
|
189 | 189 | # deletes only notification2user |
|
190 | 190 | NotificationModel().delete( |
|
191 | 191 | self._rhodecode_user.user_id, notification) |
|
192 | 192 | Session().commit() |
|
193 | 193 | return 'ok' |
|
194 | 194 | except Exception: |
|
195 | 195 | Session().rollback() |
|
196 | 196 | log.exception("Exception deleting a notification item") |
|
197 | 197 | |
|
198 | 198 | raise HTTPInternalServerError() |
@@ -1,154 +1,154 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | |
|
3 | 3 | # Copyright (C) 2016-2017 RhodeCode GmbH |
|
4 | 4 | # |
|
5 | 5 | # This program is free software: you can redistribute it and/or modify |
|
6 | 6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
7 | 7 | # (only), as published by the Free Software Foundation. |
|
8 | 8 | # |
|
9 | 9 | # This program is distributed in the hope that it will be useful, |
|
10 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 | 12 | # GNU General Public License for more details. |
|
13 | 13 | # |
|
14 | 14 | # You should have received a copy of the GNU Affero General Public License |
|
15 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | 16 | # |
|
17 | 17 | # This program is dual-licensed. If you wish to learn more about the |
|
18 | 18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
19 | 19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
20 | 20 | |
|
21 | 21 | import logging |
|
22 | 22 | |
|
23 | 23 | from pyramid.httpexceptions import HTTPFound |
|
24 | 24 | from pyramid.view import view_config |
|
25 | 25 | |
|
26 | 26 | from rhodecode.apps._base import BaseAppView, DataGridAppView |
|
27 | 27 | from rhodecode.apps.ssh_support import SshKeyFileChangeEvent |
|
28 | 28 | from rhodecode.events import trigger |
|
29 | 29 | from rhodecode.lib import helpers as h |
|
30 | 30 | from rhodecode.lib import audit_logger |
|
31 | 31 | from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired |
|
32 | 32 | from rhodecode.model.db import IntegrityError, UserSshKeys |
|
33 | 33 | from rhodecode.model.meta import Session |
|
34 | 34 | from rhodecode.model.ssh_key import SshKeyModel |
|
35 | 35 | |
|
36 | 36 | log = logging.getLogger(__name__) |
|
37 | 37 | |
|
38 | 38 | |
|
39 | 39 | class MyAccountSshKeysView(BaseAppView, DataGridAppView): |
|
40 | 40 | |
|
41 | 41 | def load_default_context(self): |
|
42 | 42 | c = self._get_local_tmpl_context() |
|
43 | 43 | c.user = c.auth_user.get_instance() |
|
44 | 44 | |
|
45 | 45 | c.ssh_enabled = self.request.registry.settings.get( |
|
46 | 46 | 'ssh.generate_authorized_keyfile') |
|
47 | self._register_global_c(c) | |
|
47 | ||
|
48 | 48 | return c |
|
49 | 49 | |
|
50 | 50 | @LoginRequired() |
|
51 | 51 | @NotAnonymous() |
|
52 | 52 | @view_config( |
|
53 | 53 | route_name='my_account_ssh_keys', request_method='GET', |
|
54 | 54 | renderer='rhodecode:templates/admin/my_account/my_account.mako') |
|
55 | 55 | def my_account_ssh_keys(self): |
|
56 | 56 | _ = self.request.translate |
|
57 | 57 | |
|
58 | 58 | c = self.load_default_context() |
|
59 | 59 | c.active = 'ssh_keys' |
|
60 | 60 | c.default_key = self.request.GET.get('default_key') |
|
61 | 61 | c.user_ssh_keys = SshKeyModel().get_ssh_keys(c.user.user_id) |
|
62 | 62 | return self._get_template_context(c) |
|
63 | 63 | |
|
64 | 64 | @LoginRequired() |
|
65 | 65 | @NotAnonymous() |
|
66 | 66 | @view_config( |
|
67 | 67 | route_name='my_account_ssh_keys_generate', request_method='GET', |
|
68 | 68 | renderer='rhodecode:templates/admin/my_account/my_account.mako') |
|
69 | 69 | def ssh_keys_generate_keypair(self): |
|
70 | 70 | _ = self.request.translate |
|
71 | 71 | c = self.load_default_context() |
|
72 | 72 | |
|
73 | 73 | c.active = 'ssh_keys_generate' |
|
74 | 74 | comment = 'RhodeCode-SSH {}'.format(c.user.email or '') |
|
75 | 75 | c.private, c.public = SshKeyModel().generate_keypair(comment=comment) |
|
76 | 76 | c.target_form_url = h.route_path( |
|
77 | 77 | 'my_account_ssh_keys', _query=dict(default_key=c.public)) |
|
78 | 78 | return self._get_template_context(c) |
|
79 | 79 | |
|
80 | 80 | @LoginRequired() |
|
81 | 81 | @NotAnonymous() |
|
82 | 82 | @CSRFRequired() |
|
83 | 83 | @view_config( |
|
84 | 84 | route_name='my_account_ssh_keys_add', request_method='POST',) |
|
85 | 85 | def my_account_ssh_keys_add(self): |
|
86 | 86 | _ = self.request.translate |
|
87 | 87 | c = self.load_default_context() |
|
88 | 88 | |
|
89 | 89 | user_data = c.user.get_api_data() |
|
90 | 90 | key_data = self.request.POST.get('key_data') |
|
91 | 91 | description = self.request.POST.get('description') |
|
92 | 92 | |
|
93 | 93 | try: |
|
94 | 94 | if not key_data: |
|
95 | 95 | raise ValueError('Please add a valid public key') |
|
96 | 96 | |
|
97 | 97 | key = SshKeyModel().parse_key(key_data.strip()) |
|
98 | 98 | fingerprint = key.hash_md5() |
|
99 | 99 | |
|
100 | 100 | ssh_key = SshKeyModel().create( |
|
101 | 101 | c.user.user_id, fingerprint, key_data, description) |
|
102 | 102 | ssh_key_data = ssh_key.get_api_data() |
|
103 | 103 | |
|
104 | 104 | audit_logger.store_web( |
|
105 | 105 | 'user.edit.ssh_key.add', action_data={ |
|
106 | 106 | 'data': {'ssh_key': ssh_key_data, 'user': user_data}}, |
|
107 | 107 | user=self._rhodecode_user, ) |
|
108 | 108 | Session().commit() |
|
109 | 109 | |
|
110 | 110 | # Trigger an event on change of keys. |
|
111 | 111 | trigger(SshKeyFileChangeEvent(), self.request.registry) |
|
112 | 112 | |
|
113 | 113 | h.flash(_("Ssh Key successfully created"), category='success') |
|
114 | 114 | |
|
115 | 115 | except IntegrityError: |
|
116 | 116 | log.exception("Exception during ssh key saving") |
|
117 | 117 | h.flash(_('An error occurred during ssh key saving: {}').format( |
|
118 | 118 | 'Such key already exists, please use a different one'), |
|
119 | 119 | category='error') |
|
120 | 120 | except Exception as e: |
|
121 | 121 | log.exception("Exception during ssh key saving") |
|
122 | 122 | h.flash(_('An error occurred during ssh key saving: {}').format(e), |
|
123 | 123 | category='error') |
|
124 | 124 | |
|
125 | 125 | return HTTPFound(h.route_path('my_account_ssh_keys')) |
|
126 | 126 | |
|
127 | 127 | @LoginRequired() |
|
128 | 128 | @NotAnonymous() |
|
129 | 129 | @CSRFRequired() |
|
130 | 130 | @view_config( |
|
131 | 131 | route_name='my_account_ssh_keys_delete', request_method='POST') |
|
132 | 132 | def my_account_ssh_keys_delete(self): |
|
133 | 133 | _ = self.request.translate |
|
134 | 134 | c = self.load_default_context() |
|
135 | 135 | |
|
136 | 136 | user_data = c.user.get_api_data() |
|
137 | 137 | |
|
138 | 138 | del_ssh_key = self.request.POST.get('del_ssh_key') |
|
139 | 139 | |
|
140 | 140 | if del_ssh_key: |
|
141 | 141 | ssh_key = UserSshKeys.get_or_404(del_ssh_key) |
|
142 | 142 | ssh_key_data = ssh_key.get_api_data() |
|
143 | 143 | |
|
144 | 144 | SshKeyModel().delete(del_ssh_key, c.user.user_id) |
|
145 | 145 | audit_logger.store_web( |
|
146 | 146 | 'user.edit.ssh_key.delete', action_data={ |
|
147 | 147 | 'data': {'ssh_key': ssh_key_data, 'user': user_data}}, |
|
148 | 148 | user=self._rhodecode_user,) |
|
149 | 149 | Session().commit() |
|
150 | 150 | # Trigger an event on change of keys. |
|
151 | 151 | trigger(SshKeyFileChangeEvent(), self.request.registry) |
|
152 | 152 | h.flash(_("Ssh key successfully deleted"), category='success') |
|
153 | 153 | |
|
154 | 154 | return HTTPFound(h.route_path('my_account_ssh_keys')) |
@@ -1,46 +1,46 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | |
|
3 | 3 | # Copyright (C) 2016-2017 RhodeCode GmbH |
|
4 | 4 | # |
|
5 | 5 | # This program is free software: you can redistribute it and/or modify |
|
6 | 6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
7 | 7 | # (only), as published by the Free Software Foundation. |
|
8 | 8 | # |
|
9 | 9 | # This program is distributed in the hope that it will be useful, |
|
10 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 | 12 | # GNU General Public License for more details. |
|
13 | 13 | # |
|
14 | 14 | # You should have received a copy of the GNU Affero General Public License |
|
15 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | 16 | # |
|
17 | 17 | # This program is dual-licensed. If you wish to learn more about the |
|
18 | 18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
19 | 19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
20 | 20 | |
|
21 | 21 | from rhodecode.apps._base import ADMIN_PREFIX |
|
22 | 22 | |
|
23 | 23 | |
|
24 | 24 | def admin_routes(config): |
|
25 | 25 | config.add_route( |
|
26 | 26 | name='ops_ping', |
|
27 | 27 | pattern='/ping') |
|
28 | 28 | config.add_route( |
|
29 | 29 | name='ops_error_test', |
|
30 | 30 | pattern='/error') |
|
31 | 31 | config.add_route( |
|
32 | 32 | name='ops_redirect_test', |
|
33 | 33 | pattern='/redirect') |
|
34 | 34 | |
|
35 | 35 | |
|
36 | 36 | def includeme(config): |
|
37 | 37 | |
|
38 | 38 | config.include(admin_routes, route_prefix=ADMIN_PREFIX + '/ops') |
|
39 |
# make OLD entries from |
|
|
39 | # make OLD entries from <4.10.0 work | |
|
40 | 40 | config.add_route( |
|
41 | 41 | name='ops_ping_legacy', pattern=ADMIN_PREFIX + '/ping') |
|
42 | 42 | config.add_route( |
|
43 | 43 | name='ops_error_test_legacy', pattern=ADMIN_PREFIX + '/error_test') |
|
44 | 44 | |
|
45 | 45 | # Scan module for configuration decorators. |
|
46 | 46 | config.scan('.views', ignore='.tests') |
@@ -1,83 +1,83 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | |
|
3 | 3 | # Copyright (C) 2016-2017 RhodeCode GmbH |
|
4 | 4 | # |
|
5 | 5 | # This program is free software: you can redistribute it and/or modify |
|
6 | 6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
7 | 7 | # (only), as published by the Free Software Foundation. |
|
8 | 8 | # |
|
9 | 9 | # This program is distributed in the hope that it will be useful, |
|
10 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 | 12 | # GNU General Public License for more details. |
|
13 | 13 | # |
|
14 | 14 | # You should have received a copy of the GNU Affero General Public License |
|
15 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | 16 | # |
|
17 | 17 | # This program is dual-licensed. If you wish to learn more about the |
|
18 | 18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
19 | 19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
20 | 20 | |
|
21 | 21 | import time |
|
22 | 22 | import logging |
|
23 | 23 | |
|
24 | 24 | from pyramid.view import view_config |
|
25 | 25 | from pyramid.httpexceptions import HTTPFound |
|
26 | 26 | |
|
27 | 27 | from rhodecode.apps._base import BaseAppView |
|
28 | 28 | from rhodecode.lib import helpers as h |
|
29 | 29 | |
|
30 | 30 | log = logging.getLogger(__name__) |
|
31 | 31 | |
|
32 | 32 | |
|
33 | 33 | class OpsView(BaseAppView): |
|
34 | 34 | |
|
35 | 35 | def load_default_context(self): |
|
36 | 36 | c = self._get_local_tmpl_context() |
|
37 | 37 | c.user = c.auth_user.get_instance() |
|
38 | self._register_global_c(c) | |
|
38 | ||
|
39 | 39 | return c |
|
40 | 40 | |
|
41 | 41 | @view_config( |
|
42 | 42 | route_name='ops_ping', request_method='GET', |
|
43 | 43 | renderer='json_ext') |
|
44 | 44 | @view_config( |
|
45 | 45 | route_name='ops_ping_legacy', request_method='GET', |
|
46 | 46 | renderer='json_ext') |
|
47 | 47 | def ops_ping(self): |
|
48 | 48 | data = { |
|
49 | 49 | 'instance': self.request.registry.settings.get('instance_id'), |
|
50 | 50 | } |
|
51 | 51 | if getattr(self.request, 'user'): |
|
52 | 52 | data.update({ |
|
53 | 53 | 'caller_ip': self.request.user.ip_addr, |
|
54 | 54 | 'caller_name': self.request.user.username, |
|
55 | 55 | }) |
|
56 | 56 | return {'ok': data} |
|
57 | 57 | |
|
58 | 58 | @view_config( |
|
59 | 59 | route_name='ops_error_test', request_method='GET', |
|
60 | 60 | renderer='json_ext') |
|
61 | 61 | @view_config( |
|
62 | 62 | route_name='ops_error_test_legacy', request_method='GET', |
|
63 | 63 | renderer='json_ext') |
|
64 | 64 | def ops_error_test(self): |
|
65 | 65 | """ |
|
66 | 66 | Test exception handling and emails on errors |
|
67 | 67 | """ |
|
68 | 68 | class TestException(Exception): |
|
69 | 69 | pass |
|
70 | 70 | |
|
71 | 71 | msg = ('RhodeCode Enterprise test exception. ' |
|
72 | 72 | 'Generation time: {}'.format(time.time())) |
|
73 | 73 | raise TestException(msg) |
|
74 | 74 | |
|
75 | 75 | @view_config( |
|
76 | 76 | route_name='ops_redirect_test', request_method='GET', |
|
77 | 77 | renderer='json_ext') |
|
78 | 78 | def ops_redirect_test(self): |
|
79 | 79 | """ |
|
80 | 80 | Test redirect handling |
|
81 | 81 | """ |
|
82 | 82 | redirect_to = self.request.GET.get('to') or h.route_path('home') |
|
83 | 83 | raise HTTPFound(redirect_to) |
@@ -1,105 +1,105 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | |
|
3 | 3 | # Copyright (C) 2011-2017 RhodeCode GmbH |
|
4 | 4 | # |
|
5 | 5 | # This program is free software: you can redistribute it and/or modify |
|
6 | 6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
7 | 7 | # (only), as published by the Free Software Foundation. |
|
8 | 8 | # |
|
9 | 9 | # This program is distributed in the hope that it will be useful, |
|
10 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 | 12 | # GNU General Public License for more details. |
|
13 | 13 | # |
|
14 | 14 | # You should have received a copy of the GNU Affero General Public License |
|
15 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | 16 | # |
|
17 | 17 | # This program is dual-licensed. If you wish to learn more about the |
|
18 | 18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
19 | 19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
20 | 20 | |
|
21 | 21 | import logging |
|
22 | 22 | |
|
23 | 23 | from pyramid.view import view_config |
|
24 | 24 | from pyramid.httpexceptions import HTTPFound |
|
25 | 25 | |
|
26 | 26 | from rhodecode.apps._base import RepoGroupAppView |
|
27 | 27 | from rhodecode.lib import helpers as h |
|
28 | 28 | from rhodecode.lib import audit_logger |
|
29 | 29 | from rhodecode.lib.auth import ( |
|
30 | 30 | LoginRequired, CSRFRequired, HasRepoGroupPermissionAnyDecorator) |
|
31 | 31 | from rhodecode.model.repo_group import RepoGroupModel |
|
32 | 32 | from rhodecode.model.meta import Session |
|
33 | 33 | |
|
34 | 34 | log = logging.getLogger(__name__) |
|
35 | 35 | |
|
36 | 36 | |
|
37 | 37 | class RepoGroupSettingsView(RepoGroupAppView): |
|
38 | 38 | def load_default_context(self): |
|
39 | 39 | c = self._get_local_tmpl_context() |
|
40 | self._register_global_c(c) | |
|
40 | ||
|
41 | 41 | return c |
|
42 | 42 | |
|
43 | 43 | @LoginRequired() |
|
44 | 44 | @HasRepoGroupPermissionAnyDecorator('group.admin') |
|
45 | 45 | @view_config( |
|
46 | 46 | route_name='edit_repo_group_advanced', request_method='GET', |
|
47 | 47 | renderer='rhodecode:templates/admin/repo_groups/repo_group_edit.mako') |
|
48 | 48 | def edit_repo_group_advanced(self): |
|
49 | 49 | c = self.load_default_context() |
|
50 | 50 | c.active = 'advanced' |
|
51 | 51 | c.repo_group = self.db_repo_group |
|
52 | 52 | return self._get_template_context(c) |
|
53 | 53 | |
|
54 | 54 | @LoginRequired() |
|
55 | 55 | @HasRepoGroupPermissionAnyDecorator('group.admin') |
|
56 | 56 | @CSRFRequired() |
|
57 | 57 | @view_config( |
|
58 | 58 | route_name='edit_repo_group_advanced_delete', request_method='POST', |
|
59 | 59 | renderer='rhodecode:templates/admin/repo_groups/repo_group_edit.mako') |
|
60 | 60 | def edit_repo_group_delete(self): |
|
61 | 61 | _ = self.request.translate |
|
62 | 62 | _ungettext = self.request.plularize |
|
63 | 63 | c = self.load_default_context() |
|
64 | 64 | c.repo_group = self.db_repo_group |
|
65 | 65 | |
|
66 | 66 | repos = c.repo_group.repositories.all() |
|
67 | 67 | if repos: |
|
68 | 68 | msg = _ungettext( |
|
69 | 69 | 'This repository group contains %(num)d repository and cannot be deleted', |
|
70 | 70 | 'This repository group contains %(num)d repositories and cannot be' |
|
71 | 71 | ' deleted', |
|
72 | 72 | len(repos)) % {'num': len(repos)} |
|
73 | 73 | h.flash(msg, category='warning') |
|
74 | 74 | raise HTTPFound( |
|
75 | 75 | h.route_path('edit_repo_group_advanced', |
|
76 | 76 | repo_group_name=self.db_repo_group_name)) |
|
77 | 77 | |
|
78 | 78 | children = c.repo_group.children.all() |
|
79 | 79 | if children: |
|
80 | 80 | msg = _ungettext( |
|
81 | 81 | 'This repository group contains %(num)d subgroup and cannot be deleted', |
|
82 | 82 | 'This repository group contains %(num)d subgroups and cannot be deleted', |
|
83 | 83 | len(children)) % {'num': len(children)} |
|
84 | 84 | h.flash(msg, category='warning') |
|
85 | 85 | raise HTTPFound( |
|
86 | 86 | h.route_path('edit_repo_group_advanced', |
|
87 | 87 | repo_group_name=self.db_repo_group_name)) |
|
88 | 88 | |
|
89 | 89 | try: |
|
90 | 90 | old_values = c.repo_group.get_api_data() |
|
91 | 91 | RepoGroupModel().delete(self.db_repo_group_name) |
|
92 | 92 | |
|
93 | 93 | audit_logger.store_web( |
|
94 | 94 | 'repo_group.delete', action_data={'old_data': old_values}, |
|
95 | 95 | user=c.rhodecode_user) |
|
96 | 96 | |
|
97 | 97 | Session().commit() |
|
98 | 98 | h.flash(_('Removed repository group `%s`') % self.db_repo_group_name, |
|
99 | 99 | category='success') |
|
100 | 100 | except Exception: |
|
101 | 101 | log.exception("Exception during deletion of repository group") |
|
102 | 102 | h.flash(_('Error occurred during deletion of repository group %s') |
|
103 | 103 | % self.db_repo_group_name, category='error') |
|
104 | 104 | |
|
105 | 105 | raise HTTPFound(h.route_path('repo_groups')) |
@@ -1,100 +1,100 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | |
|
3 | 3 | # Copyright (C) 2011-2017 RhodeCode GmbH |
|
4 | 4 | # |
|
5 | 5 | # This program is free software: you can redistribute it and/or modify |
|
6 | 6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
7 | 7 | # (only), as published by the Free Software Foundation. |
|
8 | 8 | # |
|
9 | 9 | # This program is distributed in the hope that it will be useful, |
|
10 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 | 12 | # GNU General Public License for more details. |
|
13 | 13 | # |
|
14 | 14 | # You should have received a copy of the GNU Affero General Public License |
|
15 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | 16 | # |
|
17 | 17 | # This program is dual-licensed. If you wish to learn more about the |
|
18 | 18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
19 | 19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
20 | 20 | |
|
21 | 21 | import logging |
|
22 | 22 | |
|
23 | 23 | from pyramid.view import view_config |
|
24 | 24 | from pyramid.httpexceptions import HTTPFound |
|
25 | 25 | |
|
26 | 26 | from rhodecode.apps._base import RepoGroupAppView |
|
27 | 27 | from rhodecode.lib import helpers as h |
|
28 | 28 | from rhodecode.lib import audit_logger |
|
29 | 29 | from rhodecode.lib.auth import ( |
|
30 | 30 | LoginRequired, HasRepoGroupPermissionAnyDecorator, CSRFRequired) |
|
31 | 31 | from rhodecode.model.repo_group import RepoGroupModel |
|
32 | 32 | from rhodecode.model.forms import RepoGroupPermsForm |
|
33 | 33 | from rhodecode.model.meta import Session |
|
34 | 34 | |
|
35 | 35 | log = logging.getLogger(__name__) |
|
36 | 36 | |
|
37 | 37 | |
|
38 | 38 | class RepoGroupPermissionsView(RepoGroupAppView): |
|
39 | 39 | def load_default_context(self): |
|
40 | 40 | c = self._get_local_tmpl_context() |
|
41 | self._register_global_c(c) | |
|
41 | ||
|
42 | 42 | return c |
|
43 | 43 | |
|
44 | 44 | @LoginRequired() |
|
45 | 45 | @HasRepoGroupPermissionAnyDecorator('group.admin') |
|
46 | 46 | @view_config( |
|
47 | 47 | route_name='edit_repo_group_perms', request_method='GET', |
|
48 | 48 | renderer='rhodecode:templates/admin/repo_groups/repo_group_edit.mako') |
|
49 | 49 | def edit_repo_group_permissions(self): |
|
50 | 50 | c = self.load_default_context() |
|
51 | 51 | c.active = 'permissions' |
|
52 | 52 | c.repo_group = self.db_repo_group |
|
53 | 53 | return self._get_template_context(c) |
|
54 | 54 | |
|
55 | 55 | @LoginRequired() |
|
56 | 56 | @HasRepoGroupPermissionAnyDecorator('group.admin') |
|
57 | 57 | @CSRFRequired() |
|
58 | 58 | @view_config( |
|
59 | 59 | route_name='edit_repo_group_perms_update', request_method='POST', |
|
60 | 60 | renderer='rhodecode:templates/admin/repo_groups/repo_group_edit.mako') |
|
61 | 61 | def edit_repo_groups_permissions_update(self): |
|
62 | 62 | _ = self.request.translate |
|
63 | 63 | c = self.load_default_context() |
|
64 | 64 | c.active = 'perms' |
|
65 | 65 | c.repo_group = self.db_repo_group |
|
66 | 66 | |
|
67 | 67 | valid_recursive_choices = ['none', 'repos', 'groups', 'all'] |
|
68 | form = RepoGroupPermsForm(valid_recursive_choices)()\ | |
|
68 | form = RepoGroupPermsForm(self.request.translate, valid_recursive_choices)()\ | |
|
69 | 69 | .to_python(self.request.POST) |
|
70 | 70 | |
|
71 | 71 | if not c.rhodecode_user.is_admin: |
|
72 | 72 | if self._revoke_perms_on_yourself(form): |
|
73 | 73 | msg = _('Cannot change permission for yourself as admin') |
|
74 | 74 | h.flash(msg, category='warning') |
|
75 | 75 | raise HTTPFound( |
|
76 | 76 | h.route_path('edit_repo_group_perms', |
|
77 | 77 | group_name=self.db_repo_group_name)) |
|
78 | 78 | |
|
79 | 79 | # iterate over all members(if in recursive mode) of this groups and |
|
80 | 80 | # set the permissions ! |
|
81 | 81 | # this can be potentially heavy operation |
|
82 | 82 | changes = RepoGroupModel().update_permissions( |
|
83 | 83 | c.repo_group, |
|
84 | 84 | form['perm_additions'], form['perm_updates'], form['perm_deletions'], |
|
85 | 85 | form['recursive']) |
|
86 | 86 | |
|
87 | 87 | action_data = { |
|
88 | 88 | 'added': changes['added'], |
|
89 | 89 | 'updated': changes['updated'], |
|
90 | 90 | 'deleted': changes['deleted'], |
|
91 | 91 | } |
|
92 | 92 | audit_logger.store_web( |
|
93 | 93 | 'repo_group.edit.permissions', action_data=action_data, |
|
94 | 94 | user=c.rhodecode_user) |
|
95 | 95 | |
|
96 | 96 | Session().commit() |
|
97 | 97 | h.flash(_('Repository Group permissions updated'), category='success') |
|
98 | 98 | raise HTTPFound( |
|
99 | 99 | h.route_path('edit_repo_group_perms', |
|
100 | 100 | repo_group_name=self.db_repo_group_name)) |
@@ -1,183 +1,183 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | |
|
3 | 3 | # Copyright (C) 2011-2017 RhodeCode GmbH |
|
4 | 4 | # |
|
5 | 5 | # This program is free software: you can redistribute it and/or modify |
|
6 | 6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
7 | 7 | # (only), as published by the Free Software Foundation. |
|
8 | 8 | # |
|
9 | 9 | # This program is distributed in the hope that it will be useful, |
|
10 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 | 12 | # GNU General Public License for more details. |
|
13 | 13 | # |
|
14 | 14 | # You should have received a copy of the GNU Affero General Public License |
|
15 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | 16 | # |
|
17 | 17 | # This program is dual-licensed. If you wish to learn more about the |
|
18 | 18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
19 | 19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
20 | 20 | |
|
21 | 21 | import logging |
|
22 | 22 | import deform |
|
23 | 23 | |
|
24 | 24 | from pyramid.view import view_config |
|
25 | 25 | from pyramid.httpexceptions import HTTPFound |
|
26 | 26 | |
|
27 | 27 | from rhodecode.apps._base import RepoGroupAppView |
|
28 | 28 | from rhodecode.forms import RcForm |
|
29 | 29 | from rhodecode.lib import helpers as h |
|
30 | 30 | from rhodecode.lib import audit_logger |
|
31 | 31 | from rhodecode.lib.auth import ( |
|
32 | 32 | LoginRequired, HasPermissionAll, |
|
33 | 33 | HasRepoGroupPermissionAny, HasRepoGroupPermissionAnyDecorator, CSRFRequired) |
|
34 | 34 | from rhodecode.model.db import Session, RepoGroup |
|
35 | 35 | from rhodecode.model.scm import RepoGroupList |
|
36 | 36 | from rhodecode.model.repo_group import RepoGroupModel |
|
37 | 37 | from rhodecode.model.validation_schema.schemas import repo_group_schema |
|
38 | 38 | |
|
39 | 39 | log = logging.getLogger(__name__) |
|
40 | 40 | |
|
41 | 41 | |
|
42 | 42 | class RepoGroupSettingsView(RepoGroupAppView): |
|
43 | 43 | def load_default_context(self): |
|
44 | 44 | c = self._get_local_tmpl_context() |
|
45 | 45 | c.repo_group = self.db_repo_group |
|
46 | 46 | no_parrent = not c.repo_group.parent_group |
|
47 | 47 | can_create_in_root = self._can_create_repo_group() |
|
48 | 48 | |
|
49 | 49 | show_root_location = False |
|
50 | 50 | if no_parrent or can_create_in_root: |
|
51 | 51 | # we're global admin, we're ok and we can create TOP level groups |
|
52 | 52 | # or in case this group is already at top-level we also allow |
|
53 | 53 | # creation in root |
|
54 | 54 | show_root_location = True |
|
55 | 55 | |
|
56 | 56 | acl_groups = RepoGroupList( |
|
57 | 57 | RepoGroup.query().all(), |
|
58 | 58 | perm_set=['group.admin']) |
|
59 | 59 | c.repo_groups = RepoGroup.groups_choices( |
|
60 | 60 | groups=acl_groups, |
|
61 | 61 | show_empty_group=show_root_location) |
|
62 | 62 | # filter out current repo group |
|
63 | 63 | exclude_group_ids = [c.repo_group.group_id] |
|
64 | 64 | c.repo_groups = filter(lambda x: x[0] not in exclude_group_ids, |
|
65 | 65 | c.repo_groups) |
|
66 | 66 | c.repo_groups_choices = map(lambda k: k[0], c.repo_groups) |
|
67 | 67 | |
|
68 | 68 | parent_group = c.repo_group.parent_group |
|
69 | 69 | |
|
70 | 70 | add_parent_group = (parent_group and ( |
|
71 | 71 | parent_group.group_id not in c.repo_groups_choices)) |
|
72 | 72 | if add_parent_group: |
|
73 | 73 | c.repo_groups_choices.append(parent_group.group_id) |
|
74 | 74 | c.repo_groups.append(RepoGroup._generate_choice(parent_group)) |
|
75 | 75 | |
|
76 | self._register_global_c(c) | |
|
76 | ||
|
77 | 77 | return c |
|
78 | 78 | |
|
79 | 79 | def _can_create_repo_group(self, parent_group_id=None): |
|
80 | 80 | is_admin = HasPermissionAll('hg.admin')('group create controller') |
|
81 | 81 | create_repo_group = HasPermissionAll( |
|
82 | 82 | 'hg.repogroup.create.true')('group create controller') |
|
83 | 83 | if is_admin or (create_repo_group and not parent_group_id): |
|
84 | 84 | # we're global admin, or we have global repo group create |
|
85 | 85 | # permission |
|
86 | 86 | # we're ok and we can create TOP level groups |
|
87 | 87 | return True |
|
88 | 88 | elif parent_group_id: |
|
89 | 89 | # we check the permission if we can write to parent group |
|
90 | 90 | group = RepoGroup.get(parent_group_id) |
|
91 | 91 | group_name = group.group_name if group else None |
|
92 | 92 | if HasRepoGroupPermissionAny('group.admin')( |
|
93 | 93 | group_name, 'check if user is an admin of group'): |
|
94 | 94 | # we're an admin of passed in group, we're ok. |
|
95 | 95 | return True |
|
96 | 96 | else: |
|
97 | 97 | return False |
|
98 | 98 | return False |
|
99 | 99 | |
|
100 | 100 | def _get_schema(self, c, old_values=None): |
|
101 | 101 | return repo_group_schema.RepoGroupSettingsSchema().bind( |
|
102 | 102 | repo_group_repo_group_options=c.repo_groups_choices, |
|
103 | 103 | repo_group_repo_group_items=c.repo_groups, |
|
104 | 104 | |
|
105 | 105 | # user caller |
|
106 | 106 | user=self._rhodecode_user, |
|
107 | 107 | old_values=old_values |
|
108 | 108 | ) |
|
109 | 109 | |
|
110 | 110 | @LoginRequired() |
|
111 | 111 | @HasRepoGroupPermissionAnyDecorator('group.admin') |
|
112 | 112 | @view_config( |
|
113 | 113 | route_name='edit_repo_group', request_method='GET', |
|
114 | 114 | renderer='rhodecode:templates/admin/repo_groups/repo_group_edit.mako') |
|
115 | 115 | def edit_settings(self): |
|
116 | 116 | c = self.load_default_context() |
|
117 | 117 | c.active = 'settings' |
|
118 | 118 | |
|
119 | 119 | defaults = RepoGroupModel()._get_defaults(self.db_repo_group_name) |
|
120 | 120 | defaults['repo_group_owner'] = defaults['user'] |
|
121 | 121 | |
|
122 | 122 | schema = self._get_schema(c) |
|
123 | 123 | c.form = RcForm(schema, appstruct=defaults) |
|
124 | 124 | return self._get_template_context(c) |
|
125 | 125 | |
|
126 | 126 | @LoginRequired() |
|
127 | 127 | @HasRepoGroupPermissionAnyDecorator('group.admin') |
|
128 | 128 | @CSRFRequired() |
|
129 | 129 | @view_config( |
|
130 | 130 | route_name='edit_repo_group', request_method='POST', |
|
131 | 131 | renderer='rhodecode:templates/admin/repo_groups/repo_group_edit.mako') |
|
132 | 132 | def edit_settings_update(self): |
|
133 | 133 | _ = self.request.translate |
|
134 | 134 | c = self.load_default_context() |
|
135 | 135 | c.active = 'settings' |
|
136 | 136 | |
|
137 | 137 | old_repo_group_name = self.db_repo_group_name |
|
138 | 138 | new_repo_group_name = old_repo_group_name |
|
139 | 139 | |
|
140 | 140 | old_values = RepoGroupModel()._get_defaults(self.db_repo_group_name) |
|
141 | 141 | schema = self._get_schema(c, old_values=old_values) |
|
142 | 142 | |
|
143 | 143 | c.form = RcForm(schema) |
|
144 | 144 | pstruct = self.request.POST.items() |
|
145 | 145 | |
|
146 | 146 | try: |
|
147 | 147 | schema_data = c.form.validate(pstruct) |
|
148 | 148 | except deform.ValidationFailure as err_form: |
|
149 | 149 | return self._get_template_context(c) |
|
150 | 150 | |
|
151 | 151 | # data is now VALID, proceed with updates |
|
152 | 152 | # save validated data back into the updates dict |
|
153 | 153 | validated_updates = dict( |
|
154 | 154 | group_name=schema_data['repo_group']['repo_group_name_without_group'], |
|
155 | 155 | group_parent_id=schema_data['repo_group']['repo_group_id'], |
|
156 | 156 | user=schema_data['repo_group_owner'], |
|
157 | 157 | group_description=schema_data['repo_group_description'], |
|
158 | 158 | enable_locking=schema_data['repo_group_enable_locking'], |
|
159 | 159 | ) |
|
160 | 160 | |
|
161 | 161 | try: |
|
162 | 162 | RepoGroupModel().update(self.db_repo_group, validated_updates) |
|
163 | 163 | |
|
164 | 164 | audit_logger.store_web( |
|
165 | 165 | 'repo_group.edit', action_data={'old_data': old_values}, |
|
166 | 166 | user=c.rhodecode_user) |
|
167 | 167 | |
|
168 | 168 | Session().commit() |
|
169 | 169 | |
|
170 | 170 | # use the new full name for redirect once we know we updated |
|
171 | 171 | # the name on filesystem and in DB |
|
172 | 172 | new_repo_group_name = schema_data['repo_group']['repo_group_name_with_group'] |
|
173 | 173 | |
|
174 | 174 | h.flash(_('Repository Group `{}` updated successfully').format( |
|
175 | 175 | old_repo_group_name), category='success') |
|
176 | 176 | |
|
177 | 177 | except Exception: |
|
178 | 178 | log.exception("Exception during update or repository group") |
|
179 | 179 | h.flash(_('Error occurred during update of repository group %s') |
|
180 | 180 | % old_repo_group_name, category='error') |
|
181 | 181 | |
|
182 | 182 | raise HTTPFound( |
|
183 | 183 | h.route_path('edit_repo_group', repo_group_name=new_repo_group_name)) |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | 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 |
|
1 | NO CONTENT: file was removed |
|
1 | 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 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | 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