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

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

@@ -1,276 +1,255 b''
1 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 Pylons = super.buildPythonPackage {
188 name = "Pylons-1.0.2.dev20171106";
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.bsdOriginal ];
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-2.2.10";
461 name = "celery-4.1.0";
462 462 buildInputs = with self; [];
463 463 doCheck = false;
464 propagatedBuildInputs = with self; [python-dateutil anyjson kombu pyparsing];
464 propagatedBuildInputs = with self; [pytz billiard kombu];
465 465 src = fetchurl {
466 url = "https://pypi.python.org/packages/b1/64/860fd50e45844c83442e7953effcddeff66b2851d90b2d784f7201c111b8/celery-2.2.10.tar.gz";
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.5.1";
1007 name = "kombu-4.1.0";
1008 1008 buildInputs = with self; [];
1009 1009 doCheck = false;
1010 propagatedBuildInputs = with self; [anyjson amqplib];
1010 propagatedBuildInputs = with self; [amqp];
1011 1011 src = fetchurl {
1012 url = "https://pypi.python.org/packages/19/53/74bf2a624644b45f0850a638752514fc10a8e1cbd738f10804951a6df3f5/kombu-1.5.1.tar.gz";
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.2.1";
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/52/9c18dac21f174ad31b65e22d24297864a954e6fe65876eba3f5773d2da43/python-dateutil-2.1.tar.gz";
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-2015.4";
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/7e/1a/f43b5c92df7b156822030fed151327ea096bcf417e45acc23bd1df43472f/pytz-2015.4.zip";
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.0";
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/80/b7/31b6ae5fcb188e37f7e31abe75f9be90490a5456a72860fa6e643f8a3cbc/setuptools_scm-1.15.0.tar.gz";
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.0
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==2.2.10
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==2.1
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==2015.4
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==1.13
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, pylonsapp):
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 _register_global_c(self, tmpl_args):
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(tmpl_args)
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=False):
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 get_registry(request)
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, pylonsapp):
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, pylonsapp):
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(available_groups=available_groups,
154 can_create_in_root=can_create)()
153 repo_group_form = RepoGroupForm(
154 self.request.translate, available_groups=available_groups,
155 can_create_in_root=can_create)()
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(object):
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, pylonsapp):
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(entry, feed=True)
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(entry, feed=True)
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 = self._get_template_context(c)
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 pylons work
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