##// END OF EJS Templates
release: merge back stable branch into default
milka -
r4612:cbbdebfd merge default
parent child Browse files
Show More

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

@@ -0,0 +1,43 b''
1 |RCE| 4.23.1 |RNS|
2 ------------------
3
4 Release Date
5 ^^^^^^^^^^^^
6
7 - 2020-11-25
8
9
10 New Features
11 ^^^^^^^^^^^^
12
13
14
15 General
16 ^^^^^^^
17
18
19
20 Security
21 ^^^^^^^^
22
23
24
25 Performance
26 ^^^^^^^^^^^
27
28 -
29
30
31 Fixes
32 ^^^^^
33
34 - Comments: fixed inline comments TODO resolution
35 - Comments: fixed some styling for TODO resolution.
36 - Comments: fixed general comments live push.
37 - Comments: fixed reply-to links from emails.
38 - Reviewers: fixed some UI issues on larger screen when editing reviewers.
39
40 Upgrade notes
41 ^^^^^^^^^^^^^
42
43 - Un-scheduled release addressing problems in 4.23.X releases.
@@ -0,0 +1,48 b''
1 |RCE| 4.23.2 |RNS|
2 ------------------
3
4 Release Date
5 ^^^^^^^^^^^^
6
7 - 2020-12-06
8
9
10 New Features
11 ^^^^^^^^^^^^
12
13
14
15 General
16 ^^^^^^^
17
18 - Repo extra keys: fixed some texts to improve UI.
19
20
21 Security
22 ^^^^^^^^
23
24
25
26 Performance
27 ^^^^^^^^^^^
28
29 - Core: speed up cache loading on application startup.
30
31
32 Fixes
33 ^^^^^
34
35 - Diffs: added scroll down/scroll up helper. Fixes #5643
36 - Diffs: fixed diff rendering when a common ancestor was a different commit than the source of changes.
37 - Commits / changelog: small fixes from found problems.
38 - Comments: side-bar comments hover also shows an ID of comment now.
39 - Comments: make dismiss less prominent, and text only to not mix icons/text together.
40 - Comments: UX improvements for comment buttons.
41 - Reviewers: small ui fixes for display of review rules, and added new reviewer entries.
42 - Pull-requests: fixed source/target in PR creation, affecting how we load default reviewers based on branches.
43
44
45 Upgrade notes
46 ^^^^^^^^^^^^^
47
48 - Un-scheduled release addressing problems in 4.23.X releases.
@@ -1,72 +1,75 b''
1 1 1bd3e92b7e2e2d2024152b34bb88dff1db544a71 v4.0.0
2 2 170c5398320ea6cddd50955e88d408794c21d43a v4.0.1
3 3 c3fe200198f5aa34cf2e4066df2881a9cefe3704 v4.1.0
4 4 7fd5c850745e2ea821fb4406af5f4bff9b0a7526 v4.1.1
5 5 41c87da28a179953df86061d817bc35533c66dd2 v4.1.2
6 6 baaf9f5bcea3bae0ef12ae20c8b270482e62abb6 v4.2.0
7 7 32a70c7e56844a825f61df496ee5eaf8c3c4e189 v4.2.1
8 8 fa695cdb411d294679ac081d595ac654e5613b03 v4.3.0
9 9 0e4dc11b58cad833c513fe17bac39e6850edf959 v4.3.1
10 10 8a876f48f5cb1d018b837db28ff928500cb32cfb v4.4.0
11 11 8dd86b410b1aac086ffdfc524ef300f896af5047 v4.4.1
12 12 d2514226abc8d3b4f6fb57765f47d1b6fb360a05 v4.4.2
13 13 27d783325930af6dad2741476c0d0b1b7c8415c2 v4.5.0
14 14 7f2016f352abcbdba4a19d4039c386e9629449da v4.5.1
15 15 416fec799314c70a5c780fb28b3357b08869333a v4.5.2
16 16 27c3b85fafc83143e6678fbc3da69e1615bcac55 v4.6.0
17 17 5ad13deb9118c2a5243d4032d4d9cc174e5872db v4.6.1
18 18 2be921e01fa24bb102696ada596f87464c3666f6 v4.7.0
19 19 7198bdec29c2872c974431d55200d0398354cdb1 v4.7.1
20 20 bd1c8d230fe741c2dfd7100a0ef39fd0774fd581 v4.7.2
21 21 9731914f89765d9628dc4dddc84bc9402aa124c8 v4.8.0
22 22 c5a2b7d0e4bbdebc4a62d7b624befe375207b659 v4.9.0
23 23 d9aa3b27ac9f7e78359775c75fedf7bfece232f1 v4.9.1
24 24 4ba4d74981cec5d6b28b158f875a2540952c2f74 v4.10.0
25 25 0a6821cbd6b0b3c21503002f88800679fa35ab63 v4.10.1
26 26 434ad90ec8d621f4416074b84f6e9ce03964defb v4.10.2
27 27 68baee10e698da2724c6e0f698c03a6abb993bf2 v4.10.3
28 28 00821d3afd1dce3f4767cc353f84a17f7d5218a1 v4.10.4
29 29 22f6744ad8cc274311825f63f953e4dee2ea5cb9 v4.10.5
30 30 96eb24bea2f5f9258775245e3f09f6fa0a4dda01 v4.10.6
31 31 3121217a812c956d7dd5a5875821bd73e8002a32 v4.11.0
32 32 fa98b454715ac5b912f39e84af54345909a2a805 v4.11.1
33 33 3982abcfdcc229a723cebe52d3a9bcff10bba08e v4.11.2
34 34 33195f145db9172f0a8f1487e09207178a6ab065 v4.11.3
35 35 194c74f33e32bbae6fc4d71ec5a999cff3c13605 v4.11.4
36 36 8fbd8b0c3ddc2fa4ac9e4ca16942a03eb593df2d v4.11.5
37 37 f0609aa5d5d05a1ca2f97c3995542236131c9d8a v4.11.6
38 38 b5b30547d90d2e088472a70c84878f429ffbf40d v4.12.0
39 39 9072253aa8894d20c00b4a43dc61c2168c1eff94 v4.12.1
40 40 6a517543ea9ef9987d74371bd2a315eb0b232dc9 v4.12.2
41 41 7fc0731b024c3114be87865eda7ab621cc957e32 v4.12.3
42 42 6d531c0b068c6eda62dddceedc9f845ecb6feb6f v4.12.4
43 43 3d6bf2d81b1564830eb5e83396110d2a9a93eb1e v4.13.0
44 44 5468fc89e708bd90e413cd0d54350017abbdbc0e v4.13.1
45 45 610d621550521c314ee97b3d43473ac0bcf06fb8 v4.13.2
46 46 7dc62c090881fb5d03268141e71e0940d7c3295d v4.13.3
47 47 9151328c1c46b72ba6f00d7640d9141e75aa1ca2 v4.14.0
48 48 a47eeac5dfa41fa6779d90452affba4091c3ade8 v4.14.1
49 49 4b34ce0d2c3c10510626b3b65044939bb7a2cddf v4.15.0
50 50 14502561d22e6b70613674cd675ae9a604b7989f v4.15.1
51 51 4aaa40b605b01af78a9f6882eca561c54b525ef0 v4.15.2
52 52 797744642eca86640ed20bef2cd77445780abaec v4.16.0
53 53 6c3452c7c25ed35ff269690929e11960ed6ad7d3 v4.16.1
54 54 5d8057df561c4b6b81b6401aed7d2f911e6e77f7 v4.16.2
55 55 13acfc008896ef4c62546bab5074e8f6f89b4fa7 v4.17.0
56 56 45b9b610976f483877142fe75321808ce9ebac59 v4.17.1
57 57 ad5bd0c4bd322fdbd04bb825a3d027e08f7a3901 v4.17.2
58 58 037f5794b55a6236d68f6485a485372dde6566e0 v4.17.3
59 59 83bc3100cfd6094c1d04f475ddb299b7dc3d0b33 v4.17.4
60 60 e3de8c95baf8cc9109ca56aee8193a2cb6a54c8a v4.17.4
61 61 f37a3126570477543507f0bc9d245ce75546181a v4.18.0
62 62 71d8791463e87b64c1a18475de330ee600d37561 v4.18.1
63 63 4bd6b75dac1d25c64885d4d49385e5533f21c525 v4.18.2
64 64 12ed92fe57f2e9fc7b71dc0b65e26c2da5c7085f v4.18.3
65 65 ddef396a6567117de531d67d44c739cbbfc3eebb v4.19.0
66 66 c0c65acd73914bf4368222d510afe1161ab8c07c v4.19.1
67 67 7ac623a4a2405917e2af660d645ded662011e40d v4.19.2
68 68 ef7ffda65eeb90c3ba88590a6cb816ef9b0bc232 v4.19.3
69 69 3e635489bb7961df93b01e42454ad1a8730ae968 v4.20.0
70 70 7e2eb896a02ca7cd2cd9f0f853ef3dac3f0039e3 v4.20.1
71 71 8bb5fece08ab65986225b184e46f53d2a71729cb v4.21.0
72 72 90734aac31ee4563bbe665a43ff73190cc762275 v4.22.0
73 a9655707f7cf4146affc51c12fe5ed8e02898a57 v4.23.0
74 56310d93b33b97535908ef9c7b0985b89bb7fad2 v4.23.1
75 7637c38528fa38c1eabc1fde6a869c20995a0da7 v4.23.2
@@ -1,47 +1,49 b''
1 1 .. _nginx-proxy-conf:
2 2
3 3 Nginx Proxy Config
4 4 ------------------
5 5
6 6
7 7 Set the following properties in your ``/etc/nginx/proxy.conf`` so it does not
8 8 timeout during large pushes.
9 9
10 10 .. code-block:: nginx
11 11
12 12 proxy_redirect off;
13 13 proxy_set_header Host $http_host;
14 14
15 15 ## If you use HTTPS make sure you disable gzip compression
16 16 ## to be safe against BREACH attack.
17 17 gzip off;
18 18
19 19 # Don't buffer requests in NGINX stream them using chunked-encoding
20 20 proxy_buffering off;
21 21
22 22 ## This is also required for later GIT to use streaming.
23 23 ## Works only for Nginx 1.7.11 and newer
24 24 proxy_request_buffering off;
25 25 proxy_http_version 1.1;
26 26
27 27 ## Set this to a larger number if you experience timeouts
28 28 ## or 413 Request Entity Too Large, 10GB is enough for most cases
29 29 client_max_body_size 10240m;
30 30
31 31 ## needed for container auth
32 32 # proxy_set_header REMOTE_USER $remote_user;
33 33 # proxy_set_header X-Forwarded-User $remote_user;
34 34
35 35 proxy_set_header X-Url-Scheme $scheme;
36 36 proxy_set_header X-Host $http_host;
37 37 proxy_set_header X-Real-IP $remote_addr;
38 38 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
39 proxy_set_header X-Forwarded-Proto $proto;
40 proxy_set_header X-Url-Scheme $scheme;
39 41 proxy_set_header Proxy-host $proxy_host;
40 42
41 43 proxy_connect_timeout 7200;
42 44 proxy_send_timeout 7200;
43 45 proxy_read_timeout 7200;
44 46 proxy_buffers 8 32k;
45 47
46 48 add_header X-Frame-Options SAMEORIGIN;
47 49 add_header Strict-Transport-Security "max-age=31536000; includeSubdomains;";
@@ -1,149 +1,151 b''
1 1 .. _rhodecode-release-notes-ref:
2 2
3 3 Release Notes
4 4 =============
5 5
6 6 |RCE| 4.x Versions
7 7 ------------------
8 8
9 9 .. toctree::
10 10 :maxdepth: 1
11 11
12 release-notes-4.23.2.rst
13 release-notes-4.23.1.rst
12 14 release-notes-4.23.0.rst
13 15 release-notes-4.22.0.rst
14 16 release-notes-4.21.0.rst
15 17 release-notes-4.20.1.rst
16 18 release-notes-4.20.0.rst
17 19 release-notes-4.19.3.rst
18 20 release-notes-4.19.2.rst
19 21 release-notes-4.19.1.rst
20 22 release-notes-4.19.0.rst
21 23 release-notes-4.18.3.rst
22 24 release-notes-4.18.2.rst
23 25 release-notes-4.18.1.rst
24 26 release-notes-4.18.0.rst
25 27 release-notes-4.17.4.rst
26 28 release-notes-4.17.3.rst
27 29 release-notes-4.17.2.rst
28 30 release-notes-4.17.1.rst
29 31 release-notes-4.17.0.rst
30 32 release-notes-4.16.2.rst
31 33 release-notes-4.16.1.rst
32 34 release-notes-4.16.0.rst
33 35 release-notes-4.15.2.rst
34 36 release-notes-4.15.1.rst
35 37 release-notes-4.15.0.rst
36 38 release-notes-4.14.1.rst
37 39 release-notes-4.14.0.rst
38 40 release-notes-4.13.3.rst
39 41 release-notes-4.13.2.rst
40 42 release-notes-4.13.1.rst
41 43 release-notes-4.13.0.rst
42 44 release-notes-4.12.4.rst
43 45 release-notes-4.12.3.rst
44 46 release-notes-4.12.2.rst
45 47 release-notes-4.12.1.rst
46 48 release-notes-4.12.0.rst
47 49 release-notes-4.11.6.rst
48 50 release-notes-4.11.5.rst
49 51 release-notes-4.11.4.rst
50 52 release-notes-4.11.3.rst
51 53 release-notes-4.11.2.rst
52 54 release-notes-4.11.1.rst
53 55 release-notes-4.11.0.rst
54 56 release-notes-4.10.6.rst
55 57 release-notes-4.10.5.rst
56 58 release-notes-4.10.4.rst
57 59 release-notes-4.10.3.rst
58 60 release-notes-4.10.2.rst
59 61 release-notes-4.10.1.rst
60 62 release-notes-4.10.0.rst
61 63 release-notes-4.9.1.rst
62 64 release-notes-4.9.0.rst
63 65 release-notes-4.8.0.rst
64 66 release-notes-4.7.2.rst
65 67 release-notes-4.7.1.rst
66 68 release-notes-4.7.0.rst
67 69 release-notes-4.6.1.rst
68 70 release-notes-4.6.0.rst
69 71 release-notes-4.5.2.rst
70 72 release-notes-4.5.1.rst
71 73 release-notes-4.5.0.rst
72 74 release-notes-4.4.2.rst
73 75 release-notes-4.4.1.rst
74 76 release-notes-4.4.0.rst
75 77 release-notes-4.3.1.rst
76 78 release-notes-4.3.0.rst
77 79 release-notes-4.2.1.rst
78 80 release-notes-4.2.0.rst
79 81 release-notes-4.1.2.rst
80 82 release-notes-4.1.1.rst
81 83 release-notes-4.1.0.rst
82 84 release-notes-4.0.1.rst
83 85 release-notes-4.0.0.rst
84 86
85 87 |RCE| 3.x Versions
86 88 ------------------
87 89
88 90 .. toctree::
89 91 :maxdepth: 1
90 92
91 93 release-notes-3.8.4.rst
92 94 release-notes-3.8.3.rst
93 95 release-notes-3.8.2.rst
94 96 release-notes-3.8.1.rst
95 97 release-notes-3.8.0.rst
96 98 release-notes-3.7.1.rst
97 99 release-notes-3.7.0.rst
98 100 release-notes-3.6.1.rst
99 101 release-notes-3.6.0.rst
100 102 release-notes-3.5.2.rst
101 103 release-notes-3.5.1.rst
102 104 release-notes-3.5.0.rst
103 105 release-notes-3.4.1.rst
104 106 release-notes-3.4.0.rst
105 107 release-notes-3.3.4.rst
106 108 release-notes-3.3.3.rst
107 109 release-notes-3.3.2.rst
108 110 release-notes-3.3.1.rst
109 111 release-notes-3.3.0.rst
110 112 release-notes-3.2.3.rst
111 113 release-notes-3.2.2.rst
112 114 release-notes-3.2.1.rst
113 115 release-notes-3.2.0.rst
114 116 release-notes-3.1.1.rst
115 117 release-notes-3.1.0.rst
116 118 release-notes-3.0.2.rst
117 119 release-notes-3.0.1.rst
118 120 release-notes-3.0.0.rst
119 121
120 122 |RCE| 2.x Versions
121 123 ------------------
122 124
123 125 .. toctree::
124 126 :maxdepth: 1
125 127
126 128 release-notes-2.2.8.rst
127 129 release-notes-2.2.7.rst
128 130 release-notes-2.2.6.rst
129 131 release-notes-2.2.5.rst
130 132 release-notes-2.2.4.rst
131 133 release-notes-2.2.3.rst
132 134 release-notes-2.2.2.rst
133 135 release-notes-2.2.1.rst
134 136 release-notes-2.2.0.rst
135 137 release-notes-2.1.0.rst
136 138 release-notes-2.0.2.rst
137 139 release-notes-2.0.1.rst
138 140 release-notes-2.0.0.rst
139 141
140 142 |RCE| 1.x Versions
141 143 ------------------
142 144
143 145 .. toctree::
144 146 :maxdepth: 1
145 147
146 148 release-notes-1.7.2.rst
147 149 release-notes-1.7.1.rst
148 150 release-notes-1.7.0.rst
149 151 release-notes-1.6.0.rst
@@ -1,2509 +1,2509 b''
1 1 # Generated by pip2nix 0.8.0.dev1
2 2 # See https://github.com/johbo/pip2nix
3 3
4 4 { pkgs, fetchurl, fetchgit, fetchhg }:
5 5
6 6 self: super: {
7 7 "alembic" = super.buildPythonPackage {
8 8 name = "alembic-1.4.2";
9 9 doCheck = false;
10 10 propagatedBuildInputs = [
11 11 self."sqlalchemy"
12 12 self."mako"
13 13 self."python-editor"
14 14 self."python-dateutil"
15 15 ];
16 16 src = fetchurl {
17 17 url = "https://files.pythonhosted.org/packages/60/1e/cabc75a189de0fbb2841d0975243e59bde8b7822bacbb95008ac6fe9ad47/alembic-1.4.2.tar.gz";
18 18 sha256 = "1gsdrzx9h7wfva200qvvsc9sn4w79mk2vs0bbnzjhxi1jw2b0nh3";
19 19 };
20 20 meta = {
21 21 license = [ pkgs.lib.licenses.mit ];
22 22 };
23 23 };
24 24 "amqp" = super.buildPythonPackage {
25 25 name = "amqp-2.5.2";
26 26 doCheck = false;
27 27 propagatedBuildInputs = [
28 28 self."vine"
29 29 ];
30 30 src = fetchurl {
31 31 url = "https://files.pythonhosted.org/packages/92/1d/433541994a5a69f4ad2fff39746ddbb0bdedb0ea0d85673eb0db68a7edd9/amqp-2.5.2.tar.gz";
32 32 sha256 = "13dhhfxjrqcjybnq4zahg92mydhpg2l76nxcmq7d560687wsxwbp";
33 33 };
34 34 meta = {
35 35 license = [ pkgs.lib.licenses.bsdOriginal ];
36 36 };
37 37 };
38 38 "apispec" = super.buildPythonPackage {
39 39 name = "apispec-1.0.0";
40 40 doCheck = false;
41 41 propagatedBuildInputs = [
42 42 self."PyYAML"
43 43 ];
44 44 src = fetchurl {
45 45 url = "https://files.pythonhosted.org/packages/67/15/346c04988dd67d36007e28145504c520491930c878b1f484a97b27a8f497/apispec-1.0.0.tar.gz";
46 46 sha256 = "1712w1anvqrvadjjpvai84vbaygaxabd3zz5lxihdzwzs4gvi9sp";
47 47 };
48 48 meta = {
49 49 license = [ pkgs.lib.licenses.mit ];
50 50 };
51 51 };
52 52 "appenlight-client" = super.buildPythonPackage {
53 53 name = "appenlight-client-0.6.26";
54 54 doCheck = false;
55 55 propagatedBuildInputs = [
56 56 self."webob"
57 57 self."requests"
58 58 self."six"
59 59 ];
60 60 src = fetchurl {
61 61 url = "https://files.pythonhosted.org/packages/2e/56/418fc10379b96e795ee39a15e69a730c222818af04c3821fa354eaa859ec/appenlight_client-0.6.26.tar.gz";
62 62 sha256 = "0s9xw3sb8s3pk73k78nnq4jil3q4mk6bczfa1fmgfx61kdxl2712";
63 63 };
64 64 meta = {
65 65 license = [ pkgs.lib.licenses.bsdOriginal ];
66 66 };
67 67 };
68 68 "asn1crypto" = super.buildPythonPackage {
69 69 name = "asn1crypto-0.24.0";
70 70 doCheck = false;
71 71 src = fetchurl {
72 72 url = "https://files.pythonhosted.org/packages/fc/f1/8db7daa71f414ddabfa056c4ef792e1461ff655c2ae2928a2b675bfed6b4/asn1crypto-0.24.0.tar.gz";
73 73 sha256 = "0jaf8rf9dx1lf23xfv2cdd5h52f1qr3w8k63985bc35g3d220p4x";
74 74 };
75 75 meta = {
76 76 license = [ pkgs.lib.licenses.mit ];
77 77 };
78 78 };
79 79 "atomicwrites" = super.buildPythonPackage {
80 80 name = "atomicwrites-1.3.0";
81 81 doCheck = false;
82 82 src = fetchurl {
83 83 url = "https://files.pythonhosted.org/packages/ec/0f/cd484ac8820fed363b374af30049adc8fd13065720fd4f4c6be8a2309da7/atomicwrites-1.3.0.tar.gz";
84 84 sha256 = "19ngcscdf3jsqmpcxn6zl5b6anmsajb6izp1smcd1n02midl9abm";
85 85 };
86 86 meta = {
87 87 license = [ pkgs.lib.licenses.mit ];
88 88 };
89 89 };
90 90 "attrs" = super.buildPythonPackage {
91 91 name = "attrs-19.3.0";
92 92 doCheck = false;
93 93 src = fetchurl {
94 94 url = "https://files.pythonhosted.org/packages/98/c3/2c227e66b5e896e15ccdae2e00bbc69aa46e9a8ce8869cc5fa96310bf612/attrs-19.3.0.tar.gz";
95 95 sha256 = "0wky4h28n7xnr6xv69p9z6kv8bzn50d10c3drmd9ds8gawbcxdzp";
96 96 };
97 97 meta = {
98 98 license = [ pkgs.lib.licenses.mit ];
99 99 };
100 100 };
101 101 "babel" = super.buildPythonPackage {
102 102 name = "babel-1.3";
103 103 doCheck = false;
104 104 propagatedBuildInputs = [
105 105 self."pytz"
106 106 ];
107 107 src = fetchurl {
108 108 url = "https://files.pythonhosted.org/packages/33/27/e3978243a03a76398c384c83f7ca879bc6e8f1511233a621fcada135606e/Babel-1.3.tar.gz";
109 109 sha256 = "0bnin777lc53nxd1hp3apq410jj5wx92n08h7h4izpl4f4sx00lz";
110 110 };
111 111 meta = {
112 112 license = [ pkgs.lib.licenses.bsdOriginal ];
113 113 };
114 114 };
115 115 "backports.shutil-get-terminal-size" = super.buildPythonPackage {
116 116 name = "backports.shutil-get-terminal-size-1.0.0";
117 117 doCheck = false;
118 118 src = fetchurl {
119 119 url = "https://files.pythonhosted.org/packages/ec/9c/368086faa9c016efce5da3e0e13ba392c9db79e3ab740b763fe28620b18b/backports.shutil_get_terminal_size-1.0.0.tar.gz";
120 120 sha256 = "107cmn7g3jnbkp826zlj8rrj19fam301qvaqf0f3905f5217lgki";
121 121 };
122 122 meta = {
123 123 license = [ pkgs.lib.licenses.mit ];
124 124 };
125 125 };
126 126 "beaker" = super.buildPythonPackage {
127 127 name = "beaker-1.9.1";
128 128 doCheck = false;
129 129 propagatedBuildInputs = [
130 130 self."funcsigs"
131 131 ];
132 132 src = fetchurl {
133 133 url = "https://files.pythonhosted.org/packages/ca/14/a626188d0d0c7b55dd7cf1902046c2743bd392a7078bb53073e13280eb1e/Beaker-1.9.1.tar.gz";
134 134 sha256 = "08arsn61r255lhz6hcpn2lsiqpg30clla805ysx06wmbhvb6w9rj";
135 135 };
136 136 meta = {
137 137 license = [ pkgs.lib.licenses.bsdOriginal ];
138 138 };
139 139 };
140 140 "beautifulsoup4" = super.buildPythonPackage {
141 141 name = "beautifulsoup4-4.6.3";
142 142 doCheck = false;
143 143 src = fetchurl {
144 144 url = "https://files.pythonhosted.org/packages/88/df/86bffad6309f74f3ff85ea69344a078fc30003270c8df6894fca7a3c72ff/beautifulsoup4-4.6.3.tar.gz";
145 145 sha256 = "041dhalzjciw6qyzzq7a2k4h1yvyk76xigp35hv5ibnn448ydy4h";
146 146 };
147 147 meta = {
148 148 license = [ pkgs.lib.licenses.mit ];
149 149 };
150 150 };
151 151 "billiard" = super.buildPythonPackage {
152 152 name = "billiard-3.6.1.0";
153 153 doCheck = false;
154 154 src = fetchurl {
155 155 url = "https://files.pythonhosted.org/packages/68/1d/2aea8fbb0b1e1260a8a2e77352de2983d36d7ac01207cf14c2b9c6cc860e/billiard-3.6.1.0.tar.gz";
156 156 sha256 = "09hzy3aqi7visy4vmf4xiish61n0rq5nd3iwjydydps8yrs9r05q";
157 157 };
158 158 meta = {
159 159 license = [ pkgs.lib.licenses.bsdOriginal ];
160 160 };
161 161 };
162 162 "bleach" = super.buildPythonPackage {
163 163 name = "bleach-3.1.3";
164 164 doCheck = false;
165 165 propagatedBuildInputs = [
166 166 self."six"
167 167 self."webencodings"
168 168 ];
169 169 src = fetchurl {
170 170 url = "https://files.pythonhosted.org/packages/de/09/5267f8577a92487ed43bc694476c4629c6eca2e3c93fcf690a26bfe39e1d/bleach-3.1.3.tar.gz";
171 171 sha256 = "0al437aw4p2xp83az5hhlrp913nsf0cg6kg4qj3fjhv4wakxipzq";
172 172 };
173 173 meta = {
174 174 license = [ pkgs.lib.licenses.asl20 ];
175 175 };
176 176 };
177 177 "bumpversion" = super.buildPythonPackage {
178 178 name = "bumpversion-0.5.3";
179 179 doCheck = false;
180 180 src = fetchurl {
181 181 url = "https://files.pythonhosted.org/packages/14/41/8c9da3549f8e00c84f0432c3a8cf8ed6898374714676aab91501d48760db/bumpversion-0.5.3.tar.gz";
182 182 sha256 = "0zn7694yfipxg35ikkfh7kvgl2fissha3dnqad2c5bvsvmrwhi37";
183 183 };
184 184 meta = {
185 185 license = [ pkgs.lib.licenses.mit ];
186 186 };
187 187 };
188 188 "cachetools" = super.buildPythonPackage {
189 189 name = "cachetools-3.1.1";
190 190 doCheck = false;
191 191 src = fetchurl {
192 192 url = "https://files.pythonhosted.org/packages/ae/37/7fd45996b19200e0cb2027a0b6bef4636951c4ea111bfad36c71287247f6/cachetools-3.1.1.tar.gz";
193 193 sha256 = "16m69l6n6y1r1y7cklm92rr7v69ldig2n3lbl3j323w5jz7d78lf";
194 194 };
195 195 meta = {
196 196 license = [ pkgs.lib.licenses.mit ];
197 197 };
198 198 };
199 199 "celery" = super.buildPythonPackage {
200 200 name = "celery-4.3.0";
201 201 doCheck = false;
202 202 propagatedBuildInputs = [
203 203 self."pytz"
204 204 self."billiard"
205 205 self."kombu"
206 206 self."vine"
207 207 ];
208 208 src = fetchurl {
209 209 url = "https://files.pythonhosted.org/packages/a2/4b/d020836f751617e907e84753a41c92231cd4b673ff991b8ee9da52361323/celery-4.3.0.tar.gz";
210 210 sha256 = "1y8y0gbgkwimpxqnxq2rm5qz2vy01fvjiybnpm00y5rzd2m34iac";
211 211 };
212 212 meta = {
213 213 license = [ pkgs.lib.licenses.bsdOriginal ];
214 214 };
215 215 };
216 216 "certifi" = super.buildPythonPackage {
217 217 name = "certifi-2020.4.5.1";
218 218 doCheck = false;
219 219 src = fetchurl {
220 220 url = "https://files.pythonhosted.org/packages/b8/e2/a3a86a67c3fc8249ed305fc7b7d290ebe5e4d46ad45573884761ef4dea7b/certifi-2020.4.5.1.tar.gz";
221 221 sha256 = "06b5gfs7wmmipln8f3z928d2mmx2j4b3x7pnqmj6cvmyfh8v7z2i";
222 222 };
223 223 meta = {
224 224 license = [ pkgs.lib.licenses.mpl20 { fullName = "Mozilla Public License 2.0 (MPL 2.0)"; } ];
225 225 };
226 226 };
227 227 "cffi" = super.buildPythonPackage {
228 228 name = "cffi-1.12.3";
229 229 doCheck = false;
230 230 propagatedBuildInputs = [
231 231 self."pycparser"
232 232 ];
233 233 src = fetchurl {
234 234 url = "https://files.pythonhosted.org/packages/93/1a/ab8c62b5838722f29f3daffcc8d4bd61844aa9b5f437341cc890ceee483b/cffi-1.12.3.tar.gz";
235 235 sha256 = "0x075521fxwv0mfp4cqzk7lvmw4n94bjw601qkcv314z5s182704";
236 236 };
237 237 meta = {
238 238 license = [ pkgs.lib.licenses.mit ];
239 239 };
240 240 };
241 241 "chameleon" = super.buildPythonPackage {
242 242 name = "chameleon-2.24";
243 243 doCheck = false;
244 244 src = fetchurl {
245 245 url = "https://files.pythonhosted.org/packages/5a/9e/637379ffa13c5172b5c0e704833ffea6bf51cec7567f93fd6e903d53ed74/Chameleon-2.24.tar.gz";
246 246 sha256 = "0ykqr7syxfa6h9adjfnsv1gdsca2xzm22vmic8859n0f0j09abj5";
247 247 };
248 248 meta = {
249 249 license = [ { fullName = "BSD-like (http://repoze.org/license.html)"; } ];
250 250 };
251 251 };
252 252 "channelstream" = super.buildPythonPackage {
253 253 name = "channelstream-0.6.14";
254 254 doCheck = false;
255 255 propagatedBuildInputs = [
256 256 self."gevent"
257 257 self."ws4py"
258 258 self."marshmallow"
259 259 self."python-dateutil"
260 260 self."pyramid"
261 261 self."pyramid-jinja2"
262 262 self."pyramid-apispec"
263 263 self."itsdangerous"
264 264 self."requests"
265 265 self."six"
266 266 ];
267 267 src = fetchurl {
268 268 url = "https://files.pythonhosted.org/packages/d4/2d/86d6757ccd06ce673ee224123471da3d45251d061da7c580bfc259bad853/channelstream-0.6.14.tar.gz";
269 269 sha256 = "0qgy5j3rj6c8cslzidh32glhkrhbbdxjc008y69v8a0y3zyaz2d3";
270 270 };
271 271 meta = {
272 272 license = [ pkgs.lib.licenses.bsdOriginal ];
273 273 };
274 274 };
275 275 "chardet" = super.buildPythonPackage {
276 276 name = "chardet-3.0.4";
277 277 doCheck = false;
278 278 src = fetchurl {
279 279 url = "https://files.pythonhosted.org/packages/fc/bb/a5768c230f9ddb03acc9ef3f0d4a3cf93462473795d18e9535498c8f929d/chardet-3.0.4.tar.gz";
280 280 sha256 = "1bpalpia6r5x1kknbk11p1fzph56fmmnp405ds8icksd3knr5aw4";
281 281 };
282 282 meta = {
283 283 license = [ { fullName = "LGPL"; } { fullName = "GNU Library or Lesser General Public License (LGPL)"; } ];
284 284 };
285 285 };
286 286 "click" = super.buildPythonPackage {
287 287 name = "click-7.0";
288 288 doCheck = false;
289 289 src = fetchurl {
290 290 url = "https://files.pythonhosted.org/packages/f8/5c/f60e9d8a1e77005f664b76ff8aeaee5bc05d0a91798afd7f53fc998dbc47/Click-7.0.tar.gz";
291 291 sha256 = "1mzjixd4vjbjvzb6vylki9w1556a9qmdh35kzmq6cign46av952v";
292 292 };
293 293 meta = {
294 294 license = [ pkgs.lib.licenses.bsdOriginal ];
295 295 };
296 296 };
297 297 "colander" = super.buildPythonPackage {
298 298 name = "colander-1.7.0";
299 299 doCheck = false;
300 300 propagatedBuildInputs = [
301 301 self."translationstring"
302 302 self."iso8601"
303 303 self."enum34"
304 304 ];
305 305 src = fetchurl {
306 306 url = "https://files.pythonhosted.org/packages/db/e4/74ab06f54211917b41865cafc987ce511e35503de48da9bfe9358a1bdc3e/colander-1.7.0.tar.gz";
307 307 sha256 = "1wl1bqab307lbbcjx81i28s3yl6dlm4rf15fxawkjb6j48x1cn6p";
308 308 };
309 309 meta = {
310 310 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
311 311 };
312 312 };
313 313 "configobj" = super.buildPythonPackage {
314 314 name = "configobj-5.0.6";
315 315 doCheck = false;
316 316 propagatedBuildInputs = [
317 317 self."six"
318 318 ];
319 319 src = fetchurl {
320 320 url = "https://code.rhodecode.com/upstream/configobj/artifacts/download/0-012de99a-b1e1-4f64-a5c0-07a98a41b324.tar.gz?md5=6a513f51fe04b2c18cf84c1395a7c626";
321 321 sha256 = "0kqfrdfr14mw8yd8qwq14dv2xghpkjmd3yjsy8dfcbvpcc17xnxp";
322 322 };
323 323 meta = {
324 324 license = [ pkgs.lib.licenses.bsdOriginal ];
325 325 };
326 326 };
327 327 "configparser" = super.buildPythonPackage {
328 328 name = "configparser-4.0.2";
329 329 doCheck = false;
330 330 src = fetchurl {
331 331 url = "https://files.pythonhosted.org/packages/16/4f/48975536bd488d3a272549eb795ac4a13a5f7fcdc8995def77fbef3532ee/configparser-4.0.2.tar.gz";
332 332 sha256 = "1priacxym85yjcf68hh38w55nqswaxp71ryjyfdk222kg9l85ln7";
333 333 };
334 334 meta = {
335 335 license = [ pkgs.lib.licenses.mit ];
336 336 };
337 337 };
338 338 "contextlib2" = super.buildPythonPackage {
339 339 name = "contextlib2-0.6.0.post1";
340 340 doCheck = false;
341 341 src = fetchurl {
342 342 url = "https://files.pythonhosted.org/packages/02/54/669207eb72e3d8ae8b38aa1f0703ee87a0e9f88f30d3c0a47bebdb6de242/contextlib2-0.6.0.post1.tar.gz";
343 343 sha256 = "0bhnr2ac7wy5l85ji909gyljyk85n92w8pdvslmrvc8qih4r1x01";
344 344 };
345 345 meta = {
346 346 license = [ pkgs.lib.licenses.psfl ];
347 347 };
348 348 };
349 349 "cov-core" = super.buildPythonPackage {
350 350 name = "cov-core-1.15.0";
351 351 doCheck = false;
352 352 propagatedBuildInputs = [
353 353 self."coverage"
354 354 ];
355 355 src = fetchurl {
356 356 url = "https://files.pythonhosted.org/packages/4b/87/13e75a47b4ba1be06f29f6d807ca99638bedc6b57fa491cd3de891ca2923/cov-core-1.15.0.tar.gz";
357 357 sha256 = "0k3np9ymh06yv1ib96sb6wfsxjkqhmik8qfsn119vnhga9ywc52a";
358 358 };
359 359 meta = {
360 360 license = [ pkgs.lib.licenses.mit ];
361 361 };
362 362 };
363 363 "coverage" = super.buildPythonPackage {
364 364 name = "coverage-4.5.4";
365 365 doCheck = false;
366 366 src = fetchurl {
367 367 url = "https://files.pythonhosted.org/packages/85/d5/818d0e603685c4a613d56f065a721013e942088047ff1027a632948bdae6/coverage-4.5.4.tar.gz";
368 368 sha256 = "0p0j4di6h8k6ica7jwwj09azdcg4ycxq60i9qsskmsg94cd9yzg0";
369 369 };
370 370 meta = {
371 371 license = [ pkgs.lib.licenses.asl20 ];
372 372 };
373 373 };
374 374 "cryptography" = super.buildPythonPackage {
375 375 name = "cryptography-2.6.1";
376 376 doCheck = false;
377 377 propagatedBuildInputs = [
378 378 self."asn1crypto"
379 379 self."six"
380 380 self."cffi"
381 381 self."enum34"
382 382 self."ipaddress"
383 383 ];
384 384 src = fetchurl {
385 385 url = "https://files.pythonhosted.org/packages/07/ca/bc827c5e55918ad223d59d299fff92f3563476c3b00d0a9157d9c0217449/cryptography-2.6.1.tar.gz";
386 386 sha256 = "19iwz5avym5zl6jrrrkym1rdaa9h61j20ph4cswsqgv8xg5j3j16";
387 387 };
388 388 meta = {
389 389 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "BSD or Apache License, Version 2.0"; } pkgs.lib.licenses.asl20 ];
390 390 };
391 391 };
392 392 "cssselect" = super.buildPythonPackage {
393 393 name = "cssselect-1.0.3";
394 394 doCheck = false;
395 395 src = fetchurl {
396 396 url = "https://files.pythonhosted.org/packages/52/ea/f31e1d2e9eb130fda2a631e22eac369dc644e8807345fbed5113f2d6f92b/cssselect-1.0.3.tar.gz";
397 397 sha256 = "011jqa2jhmydhi0iz4v1w3cr540z5zas8g2bw8brdw4s4b2qnv86";
398 398 };
399 399 meta = {
400 400 license = [ pkgs.lib.licenses.bsdOriginal ];
401 401 };
402 402 };
403 403 "cssutils" = super.buildPythonPackage {
404 404 name = "cssutils-1.0.2";
405 405 doCheck = false;
406 406 src = fetchurl {
407 407 url = "https://files.pythonhosted.org/packages/5c/0b/c5f29d29c037e97043770b5e7c740b6252993e4b57f029b3cd03c78ddfec/cssutils-1.0.2.tar.gz";
408 408 sha256 = "1bxchrbqzapwijap0yhlxdil1w9bmwvgx77aizlkhc2mcxjg1z52";
409 409 };
410 410 meta = {
411 411 license = [ { fullName = "GNU Library or Lesser General Public License (LGPL)"; } { fullName = "LGPL 2.1 or later, see also http://cthedot.de/cssutils/"; } ];
412 412 };
413 413 };
414 414 "decorator" = super.buildPythonPackage {
415 415 name = "decorator-4.1.2";
416 416 doCheck = false;
417 417 src = fetchurl {
418 418 url = "https://files.pythonhosted.org/packages/bb/e0/f6e41e9091e130bf16d4437dabbac3993908e4d6485ecbc985ef1352db94/decorator-4.1.2.tar.gz";
419 419 sha256 = "1d8npb11kxyi36mrvjdpcjij76l5zfyrz2f820brf0l0rcw4vdkw";
420 420 };
421 421 meta = {
422 422 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "new BSD License"; } ];
423 423 };
424 424 };
425 425 "deform" = super.buildPythonPackage {
426 426 name = "deform-2.0.8";
427 427 doCheck = false;
428 428 propagatedBuildInputs = [
429 429 self."chameleon"
430 430 self."colander"
431 431 self."iso8601"
432 432 self."peppercorn"
433 433 self."translationstring"
434 434 self."zope.deprecation"
435 435 ];
436 436 src = fetchurl {
437 437 url = "https://files.pythonhosted.org/packages/21/d0/45fdf891a82722c02fc2da319cf2d1ae6b5abf9e470ad3762135a895a868/deform-2.0.8.tar.gz";
438 438 sha256 = "0wbjv98sib96649aqaygzxnrkclyy50qij2rha6fn1i4c86bfdl9";
439 439 };
440 440 meta = {
441 441 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
442 442 };
443 443 };
444 444 "defusedxml" = super.buildPythonPackage {
445 445 name = "defusedxml-0.6.0";
446 446 doCheck = false;
447 447 src = fetchurl {
448 448 url = "https://files.pythonhosted.org/packages/a4/5f/f8aa58ca0cf01cbcee728abc9d88bfeb74e95e6cb4334cfd5bed5673ea77/defusedxml-0.6.0.tar.gz";
449 449 sha256 = "1xbp8fivl3wlbyg2jrvs4lalaqv1xp9a9f29p75wdx2s2d6h717n";
450 450 };
451 451 meta = {
452 452 license = [ pkgs.lib.licenses.psfl ];
453 453 };
454 454 };
455 455 "dm.xmlsec.binding" = super.buildPythonPackage {
456 456 name = "dm.xmlsec.binding-1.3.7";
457 457 doCheck = false;
458 458 propagatedBuildInputs = [
459 459 self."setuptools"
460 460 self."lxml"
461 461 ];
462 462 src = fetchurl {
463 463 url = "https://files.pythonhosted.org/packages/2c/9e/7651982d50252692991acdae614af821fd6c79bc8dcd598ad71d55be8fc7/dm.xmlsec.binding-1.3.7.tar.gz";
464 464 sha256 = "03jjjscx1pz2nc0dwiw9nia02qbz1c6f0f9zkyr8fmvys2n5jkb3";
465 465 };
466 466 meta = {
467 467 license = [ pkgs.lib.licenses.bsdOriginal ];
468 468 };
469 469 };
470 470 "docutils" = super.buildPythonPackage {
471 471 name = "docutils-0.16";
472 472 doCheck = false;
473 473 src = fetchurl {
474 474 url = "https://files.pythonhosted.org/packages/2f/e0/3d435b34abd2d62e8206171892f174b180cd37b09d57b924ca5c2ef2219d/docutils-0.16.tar.gz";
475 475 sha256 = "1z3qliszqca9m719q3qhdkh0ghh90g500avzdgi7pl77x5h3mpn2";
476 476 };
477 477 meta = {
478 478 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 ];
479 479 };
480 480 };
481 481 "dogpile.cache" = super.buildPythonPackage {
482 482 name = "dogpile.cache-0.9.0";
483 483 doCheck = false;
484 484 propagatedBuildInputs = [
485 485 self."decorator"
486 486 ];
487 487 src = fetchurl {
488 488 url = "https://files.pythonhosted.org/packages/ac/6a/9ac405686a94b7f009a20a50070a5786b0e1aedc707b88d40d0c4b51a82e/dogpile.cache-0.9.0.tar.gz";
489 489 sha256 = "0sr1fn6b4k5bh0cscd9yi8csqxvj4ngzildav58x5p694mc86j5k";
490 490 };
491 491 meta = {
492 492 license = [ pkgs.lib.licenses.bsdOriginal ];
493 493 };
494 494 };
495 495 "dogpile.core" = super.buildPythonPackage {
496 496 name = "dogpile.core-0.4.1";
497 497 doCheck = false;
498 498 src = fetchurl {
499 499 url = "https://files.pythonhosted.org/packages/0e/77/e72abc04c22aedf874301861e5c1e761231c288b5de369c18be8f4b5c9bb/dogpile.core-0.4.1.tar.gz";
500 500 sha256 = "0xpdvg4kr1isfkrh1rfsh7za4q5a5s6l2kf9wpvndbwf3aqjyrdy";
501 501 };
502 502 meta = {
503 503 license = [ pkgs.lib.licenses.bsdOriginal ];
504 504 };
505 505 };
506 506 "ecdsa" = super.buildPythonPackage {
507 507 name = "ecdsa-0.13.2";
508 508 doCheck = false;
509 509 src = fetchurl {
510 510 url = "https://files.pythonhosted.org/packages/51/76/139bf6e9b7b6684d5891212cdbd9e0739f2bfc03f380a1a6ffa700f392ac/ecdsa-0.13.2.tar.gz";
511 511 sha256 = "116qaq7bh4lcynzi613960jhsnn19v0kmsqwahiwjfj14gx4y0sw";
512 512 };
513 513 meta = {
514 514 license = [ pkgs.lib.licenses.mit ];
515 515 };
516 516 };
517 517 "elasticsearch" = super.buildPythonPackage {
518 518 name = "elasticsearch-6.3.1";
519 519 doCheck = false;
520 520 propagatedBuildInputs = [
521 521 self."urllib3"
522 522 ];
523 523 src = fetchurl {
524 524 url = "https://files.pythonhosted.org/packages/9d/ce/c4664e8380e379a9402ecfbaf158e56396da90d520daba21cfa840e0eb71/elasticsearch-6.3.1.tar.gz";
525 525 sha256 = "12y93v0yn7a4xmf969239g8gb3l4cdkclfpbk1qc8hx5qkymrnma";
526 526 };
527 527 meta = {
528 528 license = [ pkgs.lib.licenses.asl20 ];
529 529 };
530 530 };
531 531 "elasticsearch-dsl" = super.buildPythonPackage {
532 532 name = "elasticsearch-dsl-6.3.1";
533 533 doCheck = false;
534 534 propagatedBuildInputs = [
535 535 self."six"
536 536 self."python-dateutil"
537 537 self."elasticsearch"
538 538 self."ipaddress"
539 539 ];
540 540 src = fetchurl {
541 541 url = "https://files.pythonhosted.org/packages/4c/0d/1549f50c591db6bb4e66cbcc8d34a6e537c3d89aa426b167c244fd46420a/elasticsearch-dsl-6.3.1.tar.gz";
542 542 sha256 = "1gh8a0shqi105k325hgwb9avrpdjh0mc6mxwfg9ba7g6lssb702z";
543 543 };
544 544 meta = {
545 545 license = [ pkgs.lib.licenses.asl20 ];
546 546 };
547 547 };
548 548 "elasticsearch1" = super.buildPythonPackage {
549 549 name = "elasticsearch1-1.10.0";
550 550 doCheck = false;
551 551 propagatedBuildInputs = [
552 552 self."urllib3"
553 553 ];
554 554 src = fetchurl {
555 555 url = "https://files.pythonhosted.org/packages/a6/eb/73e75f9681fa71e3157b8ee878534235d57f24ee64f0e77f8d995fb57076/elasticsearch1-1.10.0.tar.gz";
556 556 sha256 = "0g89444kd5zwql4vbvyrmi2m6l6dcj6ga98j4hqxyyyz6z20aki2";
557 557 };
558 558 meta = {
559 559 license = [ pkgs.lib.licenses.asl20 ];
560 560 };
561 561 };
562 562 "elasticsearch1-dsl" = super.buildPythonPackage {
563 563 name = "elasticsearch1-dsl-0.0.12";
564 564 doCheck = false;
565 565 propagatedBuildInputs = [
566 566 self."six"
567 567 self."python-dateutil"
568 568 self."elasticsearch1"
569 569 ];
570 570 src = fetchurl {
571 571 url = "https://files.pythonhosted.org/packages/eb/9d/785342775cb10eddc9b8d7457d618a423b4f0b89d8b2b2d1bc27190d71db/elasticsearch1-dsl-0.0.12.tar.gz";
572 572 sha256 = "0ig1ly39v93hba0z975wnhbmzwj28w6w1sqlr2g7cn5spp732bhk";
573 573 };
574 574 meta = {
575 575 license = [ pkgs.lib.licenses.asl20 ];
576 576 };
577 577 };
578 578 "elasticsearch2" = super.buildPythonPackage {
579 579 name = "elasticsearch2-2.5.1";
580 580 doCheck = false;
581 581 propagatedBuildInputs = [
582 582 self."urllib3"
583 583 ];
584 584 src = fetchurl {
585 585 url = "https://files.pythonhosted.org/packages/f6/09/f9b24aa6b1120bea371cd57ef6f57c7694cf16660469456a8be6c2bdbe22/elasticsearch2-2.5.1.tar.gz";
586 586 sha256 = "19k2znpjfyp0hrq73cz7pjyj289040xpsxsm0xhh4jfh6y551g7k";
587 587 };
588 588 meta = {
589 589 license = [ pkgs.lib.licenses.asl20 ];
590 590 };
591 591 };
592 592 "entrypoints" = super.buildPythonPackage {
593 593 name = "entrypoints-0.2.2";
594 594 doCheck = false;
595 595 propagatedBuildInputs = [
596 596 self."configparser"
597 597 ];
598 598 src = fetchurl {
599 599 url = "https://code.rhodecode.com/upstream/entrypoints/artifacts/download/0-8e9ee9e4-c4db-409c-b07e-81568fd1832d.tar.gz?md5=3a027b8ff1d257b91fe257de6c43357d";
600 600 sha256 = "0qih72n2myclanplqipqxpgpj9d2yhff1pz5d02zq1cfqyd173w5";
601 601 };
602 602 meta = {
603 603 license = [ pkgs.lib.licenses.mit ];
604 604 };
605 605 };
606 606 "enum34" = super.buildPythonPackage {
607 607 name = "enum34-1.1.10";
608 608 doCheck = false;
609 609 src = fetchurl {
610 610 url = "https://files.pythonhosted.org/packages/11/c4/2da1f4952ba476677a42f25cd32ab8aaf0e1c0d0e00b89822b835c7e654c/enum34-1.1.10.tar.gz";
611 611 sha256 = "0j7ji699fwswm4vg6w1v07fkbf8dkzdm6gfh88jvs5nqgr3sgrnc";
612 612 };
613 613 meta = {
614 614 license = [ pkgs.lib.licenses.bsdOriginal ];
615 615 };
616 616 };
617 617 "formencode" = super.buildPythonPackage {
618 618 name = "formencode-1.2.4";
619 619 doCheck = false;
620 620 src = fetchurl {
621 621 url = "https://files.pythonhosted.org/packages/8e/59/0174271a6f004512e0201188593e6d319db139d14cb7490e488bbb078015/FormEncode-1.2.4.tar.gz";
622 622 sha256 = "1fgy04sdy4yry5xcjls3x3xy30dqwj58ycnkndim819jx0788w42";
623 623 };
624 624 meta = {
625 625 license = [ pkgs.lib.licenses.psfl ];
626 626 };
627 627 };
628 628 "funcsigs" = super.buildPythonPackage {
629 629 name = "funcsigs-1.0.2";
630 630 doCheck = false;
631 631 src = fetchurl {
632 632 url = "https://files.pythonhosted.org/packages/94/4a/db842e7a0545de1cdb0439bb80e6e42dfe82aaeaadd4072f2263a4fbed23/funcsigs-1.0.2.tar.gz";
633 633 sha256 = "0l4g5818ffyfmfs1a924811azhjj8ax9xd1cffr1mzd3ycn0zfx7";
634 634 };
635 635 meta = {
636 636 license = [ { fullName = "ASL"; } pkgs.lib.licenses.asl20 ];
637 637 };
638 638 };
639 639 "functools32" = super.buildPythonPackage {
640 640 name = "functools32-3.2.3.post2";
641 641 doCheck = false;
642 642 src = fetchurl {
643 643 url = "https://files.pythonhosted.org/packages/c5/60/6ac26ad05857c601308d8fb9e87fa36d0ebf889423f47c3502ef034365db/functools32-3.2.3-2.tar.gz";
644 644 sha256 = "0v8ya0b58x47wp216n1zamimv4iw57cxz3xxhzix52jkw3xks9gn";
645 645 };
646 646 meta = {
647 647 license = [ pkgs.lib.licenses.psfl ];
648 648 };
649 649 };
650 650 "future" = super.buildPythonPackage {
651 651 name = "future-0.14.3";
652 652 doCheck = false;
653 653 src = fetchurl {
654 654 url = "https://files.pythonhosted.org/packages/83/80/8ef3a11a15f8eaafafa0937b20c1b3f73527e69ab6b3fa1cf94a5a96aabb/future-0.14.3.tar.gz";
655 655 sha256 = "1savk7jx7hal032f522c5ajhh8fra6gmnadrj9adv5qxi18pv1b2";
656 656 };
657 657 meta = {
658 658 license = [ { fullName = "OSI Approved"; } pkgs.lib.licenses.mit ];
659 659 };
660 660 };
661 661 "futures" = super.buildPythonPackage {
662 662 name = "futures-3.0.2";
663 663 doCheck = false;
664 664 src = fetchurl {
665 665 url = "https://files.pythonhosted.org/packages/f8/e7/fc0fcbeb9193ba2d4de00b065e7fd5aecd0679e93ce95a07322b2b1434f4/futures-3.0.2.tar.gz";
666 666 sha256 = "0mz2pbgxbc2nbib1szifi07whjbfs4r02pv2z390z7p410awjgyw";
667 667 };
668 668 meta = {
669 669 license = [ pkgs.lib.licenses.bsdOriginal ];
670 670 };
671 671 };
672 672 "gevent" = super.buildPythonPackage {
673 673 name = "gevent-1.5.0";
674 674 doCheck = false;
675 675 propagatedBuildInputs = [
676 676 self."greenlet"
677 677 ];
678 678 src = fetchurl {
679 679 url = "https://files.pythonhosted.org/packages/5a/79/2c63d385d017b5dd7d70983a463dfd25befae70c824fedb857df6e72eff2/gevent-1.5.0.tar.gz";
680 680 sha256 = "0aac3d4vhv5n4rsb6cqzq0d1xx9immqz4fmpddw35yxkwdc450dj";
681 681 };
682 682 meta = {
683 683 license = [ pkgs.lib.licenses.mit ];
684 684 };
685 685 };
686 686 "gnureadline" = super.buildPythonPackage {
687 687 name = "gnureadline-6.3.8";
688 688 doCheck = false;
689 689 src = fetchurl {
690 690 url = "https://files.pythonhosted.org/packages/50/64/86085c823cd78f9df9d8e33dce0baa71618016f8860460b82cf6610e1eb3/gnureadline-6.3.8.tar.gz";
691 691 sha256 = "0ddhj98x2nv45iz4aadk4b9m0b1kpsn1xhcbypn5cd556knhiqjq";
692 692 };
693 693 meta = {
694 694 license = [ { fullName = "GNU General Public License v3 (GPLv3)"; } pkgs.lib.licenses.gpl1 ];
695 695 };
696 696 };
697 697 "gprof2dot" = super.buildPythonPackage {
698 698 name = "gprof2dot-2017.9.19";
699 699 doCheck = false;
700 700 src = fetchurl {
701 701 url = "https://files.pythonhosted.org/packages/9d/36/f977122502979f3dfb50704979c9ed70e6b620787942b089bf1af15f5aba/gprof2dot-2017.9.19.tar.gz";
702 702 sha256 = "17ih23ld2nzgc3xwgbay911l6lh96jp1zshmskm17n1gg2i7mg6f";
703 703 };
704 704 meta = {
705 705 license = [ { fullName = "GNU Lesser General Public License v3 or later (LGPLv3+)"; } { fullName = "LGPL"; } ];
706 706 };
707 707 };
708 708 "greenlet" = super.buildPythonPackage {
709 709 name = "greenlet-0.4.15";
710 710 doCheck = false;
711 711 src = fetchurl {
712 712 url = "https://files.pythonhosted.org/packages/f8/e8/b30ae23b45f69aa3f024b46064c0ac8e5fcb4f22ace0dca8d6f9c8bbe5e7/greenlet-0.4.15.tar.gz";
713 713 sha256 = "1g4g1wwc472ds89zmqlpyan3fbnzpa8qm48z3z1y6mlk44z485ll";
714 714 };
715 715 meta = {
716 716 license = [ pkgs.lib.licenses.mit ];
717 717 };
718 718 };
719 719 "gunicorn" = super.buildPythonPackage {
720 720 name = "gunicorn-19.9.0";
721 721 doCheck = false;
722 722 src = fetchurl {
723 723 url = "https://files.pythonhosted.org/packages/47/52/68ba8e5e8ba251e54006a49441f7ccabca83b6bef5aedacb4890596c7911/gunicorn-19.9.0.tar.gz";
724 724 sha256 = "1wzlf4xmn6qjirh5w81l6i6kqjnab1n1qqkh7zsj1yb6gh4n49ps";
725 725 };
726 726 meta = {
727 727 license = [ pkgs.lib.licenses.mit ];
728 728 };
729 729 };
730 730 "hupper" = super.buildPythonPackage {
731 731 name = "hupper-1.10.2";
732 732 doCheck = false;
733 733 src = fetchurl {
734 734 url = "https://files.pythonhosted.org/packages/41/24/ea90fef04706e54bd1635c05c50dc9cf87cda543c59303a03e7aa7dda0ce/hupper-1.10.2.tar.gz";
735 735 sha256 = "0am0p6g5cz6xmcaf04xq8q6dzdd9qz0phj6gcmpsckf2mcyza61q";
736 736 };
737 737 meta = {
738 738 license = [ pkgs.lib.licenses.mit ];
739 739 };
740 740 };
741 741 "idna" = super.buildPythonPackage {
742 742 name = "idna-2.8";
743 743 doCheck = false;
744 744 src = fetchurl {
745 745 url = "https://files.pythonhosted.org/packages/ad/13/eb56951b6f7950cadb579ca166e448ba77f9d24efc03edd7e55fa57d04b7/idna-2.8.tar.gz";
746 746 sha256 = "01rlkigdxg17sf9yar1jl8n18ls59367wqh59hnawlyg53vb6my3";
747 747 };
748 748 meta = {
749 749 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "BSD-like"; } ];
750 750 };
751 751 };
752 752 "importlib-metadata" = super.buildPythonPackage {
753 753 name = "importlib-metadata-1.6.0";
754 754 doCheck = false;
755 755 propagatedBuildInputs = [
756 756 self."zipp"
757 757 self."pathlib2"
758 758 self."contextlib2"
759 759 self."configparser"
760 760 ];
761 761 src = fetchurl {
762 762 url = "https://files.pythonhosted.org/packages/b4/1b/baab42e3cd64c9d5caac25a9d6c054f8324cdc38975a44d600569f1f7158/importlib_metadata-1.6.0.tar.gz";
763 763 sha256 = "07icyggasn38yv2swdrd8z6i0plazmc9adavsdkbqqj91j53ll9l";
764 764 };
765 765 meta = {
766 766 license = [ pkgs.lib.licenses.asl20 ];
767 767 };
768 768 };
769 769 "infrae.cache" = super.buildPythonPackage {
770 770 name = "infrae.cache-1.0.1";
771 771 doCheck = false;
772 772 propagatedBuildInputs = [
773 773 self."beaker"
774 774 self."repoze.lru"
775 775 ];
776 776 src = fetchurl {
777 777 url = "https://files.pythonhosted.org/packages/bb/f0/e7d5e984cf6592fd2807dc7bc44a93f9d18e04e6a61f87fdfb2622422d74/infrae.cache-1.0.1.tar.gz";
778 778 sha256 = "1dvqsjn8vw253wz9d1pz17j79mf4bs53dvp2qxck2qdp1am1njw4";
779 779 };
780 780 meta = {
781 781 license = [ pkgs.lib.licenses.zpl21 ];
782 782 };
783 783 };
784 784 "invoke" = super.buildPythonPackage {
785 785 name = "invoke-0.13.0";
786 786 doCheck = false;
787 787 src = fetchurl {
788 788 url = "https://files.pythonhosted.org/packages/47/bf/d07ef52fa1ac645468858bbac7cb95b246a972a045e821493d17d89c81be/invoke-0.13.0.tar.gz";
789 789 sha256 = "0794vhgxfmkh0vzkkg5cfv1w82g3jc3xr18wim29far9qpx9468s";
790 790 };
791 791 meta = {
792 792 license = [ pkgs.lib.licenses.bsdOriginal ];
793 793 };
794 794 };
795 795 "ipaddress" = super.buildPythonPackage {
796 796 name = "ipaddress-1.0.23";
797 797 doCheck = false;
798 798 src = fetchurl {
799 799 url = "https://files.pythonhosted.org/packages/b9/9a/3e9da40ea28b8210dd6504d3fe9fe7e013b62bf45902b458d1cdc3c34ed9/ipaddress-1.0.23.tar.gz";
800 800 sha256 = "1qp743h30s04m3cg3yk3fycad930jv17q7dsslj4mfw0jlvf1y5p";
801 801 };
802 802 meta = {
803 803 license = [ pkgs.lib.licenses.psfl ];
804 804 };
805 805 };
806 806 "ipdb" = super.buildPythonPackage {
807 807 name = "ipdb-0.13.2";
808 808 doCheck = false;
809 809 propagatedBuildInputs = [
810 810 self."setuptools"
811 811 self."ipython"
812 812 ];
813 813 src = fetchurl {
814 814 url = "https://files.pythonhosted.org/packages/2c/bb/a3e1a441719ebd75c6dac8170d3ddba884b7ee8a5c0f9aefa7297386627a/ipdb-0.13.2.tar.gz";
815 815 sha256 = "0jcd849rx30y3wcgzsqbn06v0yjlzvb9x3076q0yxpycdwm1ryvp";
816 816 };
817 817 meta = {
818 818 license = [ pkgs.lib.licenses.bsdOriginal ];
819 819 };
820 820 };
821 821 "ipython" = super.buildPythonPackage {
822 822 name = "ipython-5.1.0";
823 823 doCheck = false;
824 824 propagatedBuildInputs = [
825 825 self."setuptools"
826 826 self."decorator"
827 827 self."pickleshare"
828 828 self."simplegeneric"
829 829 self."traitlets"
830 830 self."prompt-toolkit"
831 831 self."pygments"
832 832 self."pexpect"
833 833 self."backports.shutil-get-terminal-size"
834 834 self."pathlib2"
835 835 self."pexpect"
836 836 ];
837 837 src = fetchurl {
838 838 url = "https://files.pythonhosted.org/packages/89/63/a9292f7cd9d0090a0f995e1167f3f17d5889dcbc9a175261719c513b9848/ipython-5.1.0.tar.gz";
839 839 sha256 = "0qdrf6aj9kvjczd5chj1my8y2iq09am9l8bb2a1334a52d76kx3y";
840 840 };
841 841 meta = {
842 842 license = [ pkgs.lib.licenses.bsdOriginal ];
843 843 };
844 844 };
845 845 "ipython-genutils" = super.buildPythonPackage {
846 846 name = "ipython-genutils-0.2.0";
847 847 doCheck = false;
848 848 src = fetchurl {
849 849 url = "https://files.pythonhosted.org/packages/e8/69/fbeffffc05236398ebfcfb512b6d2511c622871dca1746361006da310399/ipython_genutils-0.2.0.tar.gz";
850 850 sha256 = "1a4bc9y8hnvq6cp08qs4mckgm6i6ajpndp4g496rvvzcfmp12bpb";
851 851 };
852 852 meta = {
853 853 license = [ pkgs.lib.licenses.bsdOriginal ];
854 854 };
855 855 };
856 856 "iso8601" = super.buildPythonPackage {
857 857 name = "iso8601-0.1.12";
858 858 doCheck = false;
859 859 src = fetchurl {
860 860 url = "https://files.pythonhosted.org/packages/45/13/3db24895497345fb44c4248c08b16da34a9eb02643cea2754b21b5ed08b0/iso8601-0.1.12.tar.gz";
861 861 sha256 = "10nyvvnrhw2w3p09v1ica4lgj6f4g9j3kkfx17qmraiq3w7b5i29";
862 862 };
863 863 meta = {
864 864 license = [ pkgs.lib.licenses.mit ];
865 865 };
866 866 };
867 867 "isodate" = super.buildPythonPackage {
868 868 name = "isodate-0.6.0";
869 869 doCheck = false;
870 870 propagatedBuildInputs = [
871 871 self."six"
872 872 ];
873 873 src = fetchurl {
874 874 url = "https://files.pythonhosted.org/packages/b1/80/fb8c13a4cd38eb5021dc3741a9e588e4d1de88d895c1910c6fc8a08b7a70/isodate-0.6.0.tar.gz";
875 875 sha256 = "1n7jkz68kk5pwni540pr5zdh99bf6ywydk1p5pdrqisrawylldif";
876 876 };
877 877 meta = {
878 878 license = [ pkgs.lib.licenses.bsdOriginal ];
879 879 };
880 880 };
881 881 "itsdangerous" = super.buildPythonPackage {
882 882 name = "itsdangerous-1.1.0";
883 883 doCheck = false;
884 884 src = fetchurl {
885 885 url = "https://files.pythonhosted.org/packages/68/1a/f27de07a8a304ad5fa817bbe383d1238ac4396da447fa11ed937039fa04b/itsdangerous-1.1.0.tar.gz";
886 886 sha256 = "068zpbksq5q2z4dckh2k1zbcq43ay74ylqn77rni797j0wyh66rj";
887 887 };
888 888 meta = {
889 889 license = [ pkgs.lib.licenses.bsdOriginal ];
890 890 };
891 891 };
892 892 "jinja2" = super.buildPythonPackage {
893 893 name = "jinja2-2.9.6";
894 894 doCheck = false;
895 895 propagatedBuildInputs = [
896 896 self."markupsafe"
897 897 ];
898 898 src = fetchurl {
899 899 url = "https://files.pythonhosted.org/packages/90/61/f820ff0076a2599dd39406dcb858ecb239438c02ce706c8e91131ab9c7f1/Jinja2-2.9.6.tar.gz";
900 900 sha256 = "1zzrkywhziqffrzks14kzixz7nd4yh2vc0fb04a68vfd2ai03anx";
901 901 };
902 902 meta = {
903 903 license = [ pkgs.lib.licenses.bsdOriginal ];
904 904 };
905 905 };
906 906 "jsonschema" = super.buildPythonPackage {
907 907 name = "jsonschema-2.6.0";
908 908 doCheck = false;
909 909 propagatedBuildInputs = [
910 910 self."functools32"
911 911 ];
912 912 src = fetchurl {
913 913 url = "https://files.pythonhosted.org/packages/58/b9/171dbb07e18c6346090a37f03c7e74410a1a56123f847efed59af260a298/jsonschema-2.6.0.tar.gz";
914 914 sha256 = "00kf3zmpp9ya4sydffpifn0j0mzm342a2vzh82p6r0vh10cg7xbg";
915 915 };
916 916 meta = {
917 917 license = [ pkgs.lib.licenses.mit ];
918 918 };
919 919 };
920 920 "jupyter-client" = super.buildPythonPackage {
921 921 name = "jupyter-client-5.0.0";
922 922 doCheck = false;
923 923 propagatedBuildInputs = [
924 924 self."traitlets"
925 925 self."jupyter-core"
926 926 self."pyzmq"
927 927 self."python-dateutil"
928 928 ];
929 929 src = fetchurl {
930 930 url = "https://files.pythonhosted.org/packages/e5/6f/65412ed462202b90134b7e761b0b7e7f949e07a549c1755475333727b3d0/jupyter_client-5.0.0.tar.gz";
931 931 sha256 = "0nxw4rqk4wsjhc87gjqd7pv89cb9dnimcfnmcmp85bmrvv1gjri7";
932 932 };
933 933 meta = {
934 934 license = [ pkgs.lib.licenses.bsdOriginal ];
935 935 };
936 936 };
937 937 "jupyter-core" = super.buildPythonPackage {
938 938 name = "jupyter-core-4.5.0";
939 939 doCheck = false;
940 940 propagatedBuildInputs = [
941 941 self."traitlets"
942 942 ];
943 943 src = fetchurl {
944 944 url = "https://files.pythonhosted.org/packages/4a/de/ff4ca734656d17ebe0450807b59d728f45277e2e7f4b82bc9aae6cb82961/jupyter_core-4.5.0.tar.gz";
945 945 sha256 = "1xr4pbghwk5hayn5wwnhb7z95380r45p79gf5if5pi1akwg7qvic";
946 946 };
947 947 meta = {
948 948 license = [ pkgs.lib.licenses.bsdOriginal ];
949 949 };
950 950 };
951 951 "kombu" = super.buildPythonPackage {
952 952 name = "kombu-4.6.6";
953 953 doCheck = false;
954 954 propagatedBuildInputs = [
955 955 self."amqp"
956 956 self."importlib-metadata"
957 957 ];
958 958 src = fetchurl {
959 959 url = "https://files.pythonhosted.org/packages/20/e6/bc2d9affba6138a1dc143f77fef253e9e08e238fa7c0688d917c09005e96/kombu-4.6.6.tar.gz";
960 960 sha256 = "11mxpcy8mg1l35bgbhba70v29bydr2hrhdbdlb4lg98m3m5vaq0p";
961 961 };
962 962 meta = {
963 963 license = [ pkgs.lib.licenses.bsdOriginal ];
964 964 };
965 965 };
966 966 "lxml" = super.buildPythonPackage {
967 967 name = "lxml-4.2.5";
968 968 doCheck = false;
969 969 src = fetchurl {
970 970 url = "https://files.pythonhosted.org/packages/4b/20/ddf5eb3bd5c57582d2b4652b4bbcf8da301bdfe5d805cb94e805f4d7464d/lxml-4.2.5.tar.gz";
971 971 sha256 = "0zw0y9hs0nflxhl9cs6ipwwh53szi3w2x06wl0k9cylyqac0cwin";
972 972 };
973 973 meta = {
974 974 license = [ pkgs.lib.licenses.bsdOriginal ];
975 975 };
976 976 };
977 977 "mako" = super.buildPythonPackage {
978 978 name = "mako-1.1.0";
979 979 doCheck = false;
980 980 propagatedBuildInputs = [
981 981 self."markupsafe"
982 982 ];
983 983 src = fetchurl {
984 984 url = "https://files.pythonhosted.org/packages/b0/3c/8dcd6883d009f7cae0f3157fb53e9afb05a0d3d33b3db1268ec2e6f4a56b/Mako-1.1.0.tar.gz";
985 985 sha256 = "0jqa3qfpykyn4fmkn0kh6043sfls7br8i2bsdbccazcvk9cijsd3";
986 986 };
987 987 meta = {
988 988 license = [ pkgs.lib.licenses.mit ];
989 989 };
990 990 };
991 991 "markdown" = super.buildPythonPackage {
992 992 name = "markdown-2.6.11";
993 993 doCheck = false;
994 994 src = fetchurl {
995 995 url = "https://files.pythonhosted.org/packages/b3/73/fc5c850f44af5889192dff783b7b0d8f3fe8d30b65c8e3f78f8f0265fecf/Markdown-2.6.11.tar.gz";
996 996 sha256 = "108g80ryzykh8bj0i7jfp71510wrcixdi771lf2asyghgyf8cmm8";
997 997 };
998 998 meta = {
999 999 license = [ pkgs.lib.licenses.bsdOriginal ];
1000 1000 };
1001 1001 };
1002 1002 "markupsafe" = super.buildPythonPackage {
1003 1003 name = "markupsafe-1.1.1";
1004 1004 doCheck = false;
1005 1005 src = fetchurl {
1006 1006 url = "https://files.pythonhosted.org/packages/b9/2e/64db92e53b86efccfaea71321f597fa2e1b2bd3853d8ce658568f7a13094/MarkupSafe-1.1.1.tar.gz";
1007 1007 sha256 = "0sqipg4fk7xbixqd8kq6rlkxj664d157bdwbh93farcphf92x1r9";
1008 1008 };
1009 1009 meta = {
1010 1010 license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.bsd3 ];
1011 1011 };
1012 1012 };
1013 1013 "marshmallow" = super.buildPythonPackage {
1014 1014 name = "marshmallow-2.18.0";
1015 1015 doCheck = false;
1016 1016 src = fetchurl {
1017 1017 url = "https://files.pythonhosted.org/packages/ad/0b/5799965d1c6d5f608d684e2c0dce8a828e0309a3bfe8327d9418a89f591c/marshmallow-2.18.0.tar.gz";
1018 1018 sha256 = "1g0aafpjn7yaxq06yndy8c7rs9n42adxkqq1ayhlr869pr06d3lm";
1019 1019 };
1020 1020 meta = {
1021 1021 license = [ pkgs.lib.licenses.mit ];
1022 1022 };
1023 1023 };
1024 1024 "mistune" = super.buildPythonPackage {
1025 1025 name = "mistune-0.8.4";
1026 1026 doCheck = false;
1027 1027 src = fetchurl {
1028 1028 url = "https://files.pythonhosted.org/packages/2d/a4/509f6e7783ddd35482feda27bc7f72e65b5e7dc910eca4ab2164daf9c577/mistune-0.8.4.tar.gz";
1029 1029 sha256 = "0vkmsh0x480rni51lhyvigfdf06b9247z868pk3bal1wnnfl58sr";
1030 1030 };
1031 1031 meta = {
1032 1032 license = [ pkgs.lib.licenses.bsdOriginal ];
1033 1033 };
1034 1034 };
1035 1035 "mock" = super.buildPythonPackage {
1036 1036 name = "mock-3.0.5";
1037 1037 doCheck = false;
1038 1038 propagatedBuildInputs = [
1039 1039 self."six"
1040 1040 self."funcsigs"
1041 1041 ];
1042 1042 src = fetchurl {
1043 1043 url = "https://files.pythonhosted.org/packages/2e/ab/4fe657d78b270aa6a32f027849513b829b41b0f28d9d8d7f8c3d29ea559a/mock-3.0.5.tar.gz";
1044 1044 sha256 = "1hrp6j0yrx2xzylfv02qa8kph661m6yq4p0mc8fnimch9j4psrc3";
1045 1045 };
1046 1046 meta = {
1047 1047 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "OSI Approved :: BSD License"; } ];
1048 1048 };
1049 1049 };
1050 1050 "more-itertools" = super.buildPythonPackage {
1051 1051 name = "more-itertools-5.0.0";
1052 1052 doCheck = false;
1053 1053 propagatedBuildInputs = [
1054 1054 self."six"
1055 1055 ];
1056 1056 src = fetchurl {
1057 1057 url = "https://files.pythonhosted.org/packages/dd/26/30fc0d541d9fdf55faf5ba4b0fd68f81d5bd2447579224820ad525934178/more-itertools-5.0.0.tar.gz";
1058 1058 sha256 = "1r12cm6mcdwdzz7d47a6g4l437xsvapdlgyhqay3i2nrlv03da9q";
1059 1059 };
1060 1060 meta = {
1061 1061 license = [ pkgs.lib.licenses.mit ];
1062 1062 };
1063 1063 };
1064 1064 "msgpack-python" = super.buildPythonPackage {
1065 1065 name = "msgpack-python-0.5.6";
1066 1066 doCheck = false;
1067 1067 src = fetchurl {
1068 1068 url = "https://files.pythonhosted.org/packages/8a/20/6eca772d1a5830336f84aca1d8198e5a3f4715cd1c7fc36d3cc7f7185091/msgpack-python-0.5.6.tar.gz";
1069 1069 sha256 = "16wh8qgybmfh4pjp8vfv78mdlkxfmcasg78lzlnm6nslsfkci31p";
1070 1070 };
1071 1071 meta = {
1072 1072 license = [ pkgs.lib.licenses.asl20 ];
1073 1073 };
1074 1074 };
1075 1075 "mysql-python" = super.buildPythonPackage {
1076 1076 name = "mysql-python-1.2.5";
1077 1077 doCheck = false;
1078 1078 src = fetchurl {
1079 1079 url = "https://files.pythonhosted.org/packages/a5/e9/51b544da85a36a68debe7a7091f068d802fc515a3a202652828c73453cad/MySQL-python-1.2.5.zip";
1080 1080 sha256 = "0x0c2jg0bb3pp84njaqiic050qkyd7ymwhfvhipnimg58yv40441";
1081 1081 };
1082 1082 meta = {
1083 1083 license = [ pkgs.lib.licenses.gpl1 ];
1084 1084 };
1085 1085 };
1086 1086 "nbconvert" = super.buildPythonPackage {
1087 1087 name = "nbconvert-5.3.1";
1088 1088 doCheck = false;
1089 1089 propagatedBuildInputs = [
1090 1090 self."mistune"
1091 1091 self."jinja2"
1092 1092 self."pygments"
1093 1093 self."traitlets"
1094 1094 self."jupyter-core"
1095 1095 self."nbformat"
1096 1096 self."entrypoints"
1097 1097 self."bleach"
1098 1098 self."pandocfilters"
1099 1099 self."testpath"
1100 1100 ];
1101 1101 src = fetchurl {
1102 1102 url = "https://files.pythonhosted.org/packages/b9/a4/d0a0938ad6f5eeb4dea4e73d255c617ef94b0b2849d51194c9bbdb838412/nbconvert-5.3.1.tar.gz";
1103 1103 sha256 = "1f9dkvpx186xjm4xab0qbph588mncp4vqk3fmxrsnqs43mks9c8j";
1104 1104 };
1105 1105 meta = {
1106 1106 license = [ pkgs.lib.licenses.bsdOriginal ];
1107 1107 };
1108 1108 };
1109 1109 "nbformat" = super.buildPythonPackage {
1110 1110 name = "nbformat-4.4.0";
1111 1111 doCheck = false;
1112 1112 propagatedBuildInputs = [
1113 1113 self."ipython-genutils"
1114 1114 self."traitlets"
1115 1115 self."jsonschema"
1116 1116 self."jupyter-core"
1117 1117 ];
1118 1118 src = fetchurl {
1119 1119 url = "https://files.pythonhosted.org/packages/6e/0e/160754f7ae3e984863f585a3743b0ed1702043a81245907c8fae2d537155/nbformat-4.4.0.tar.gz";
1120 1120 sha256 = "00nlf08h8yc4q73nphfvfhxrcnilaqanb8z0mdy6nxk0vzq4wjgp";
1121 1121 };
1122 1122 meta = {
1123 1123 license = [ pkgs.lib.licenses.bsdOriginal ];
1124 1124 };
1125 1125 };
1126 1126 "packaging" = super.buildPythonPackage {
1127 1127 name = "packaging-20.3";
1128 1128 doCheck = false;
1129 1129 propagatedBuildInputs = [
1130 1130 self."pyparsing"
1131 1131 self."six"
1132 1132 ];
1133 1133 src = fetchurl {
1134 1134 url = "https://files.pythonhosted.org/packages/65/37/83e3f492eb52d771e2820e88105f605335553fe10422cba9d256faeb1702/packaging-20.3.tar.gz";
1135 1135 sha256 = "18xpablq278janh03bai9xd4kz9b0yfp6vflazn725ns9x3jna9w";
1136 1136 };
1137 1137 meta = {
1138 1138 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "BSD or Apache License, Version 2.0"; } pkgs.lib.licenses.asl20 ];
1139 1139 };
1140 1140 };
1141 1141 "pandocfilters" = super.buildPythonPackage {
1142 1142 name = "pandocfilters-1.4.2";
1143 1143 doCheck = false;
1144 1144 src = fetchurl {
1145 1145 url = "https://files.pythonhosted.org/packages/4c/ea/236e2584af67bb6df960832731a6e5325fd4441de001767da328c33368ce/pandocfilters-1.4.2.tar.gz";
1146 1146 sha256 = "1a8d9b7s48gmq9zj0pmbyv2sivn5i7m6mybgpkk4jm5vd7hp1pdk";
1147 1147 };
1148 1148 meta = {
1149 1149 license = [ pkgs.lib.licenses.bsdOriginal ];
1150 1150 };
1151 1151 };
1152 1152 "paste" = super.buildPythonPackage {
1153 1153 name = "paste-3.4.0";
1154 1154 doCheck = false;
1155 1155 propagatedBuildInputs = [
1156 1156 self."six"
1157 1157 ];
1158 1158 src = fetchurl {
1159 1159 url = "https://files.pythonhosted.org/packages/79/4a/45821b71dd40000507549afd1491546afad8279c0a87527c88776a794158/Paste-3.4.0.tar.gz";
1160 1160 sha256 = "16sichvhyci1gaarkjs35mai8vphh7b244qm14hj1isw38nx4c03";
1161 1161 };
1162 1162 meta = {
1163 1163 license = [ pkgs.lib.licenses.mit ];
1164 1164 };
1165 1165 };
1166 1166 "pastedeploy" = super.buildPythonPackage {
1167 1167 name = "pastedeploy-2.1.0";
1168 1168 doCheck = false;
1169 1169 src = fetchurl {
1170 1170 url = "https://files.pythonhosted.org/packages/c4/e9/972a1c20318b3ae9edcab11a6cef64308fbae5d0d45ab52c6f8b2b8f35b8/PasteDeploy-2.1.0.tar.gz";
1171 1171 sha256 = "16qsq5y6mryslmbp5pn35x4z8z3ndp5rpgl42h226879nrw9hmg7";
1172 1172 };
1173 1173 meta = {
1174 1174 license = [ pkgs.lib.licenses.mit ];
1175 1175 };
1176 1176 };
1177 1177 "pastescript" = super.buildPythonPackage {
1178 1178 name = "pastescript-3.2.0";
1179 1179 doCheck = false;
1180 1180 propagatedBuildInputs = [
1181 1181 self."paste"
1182 1182 self."pastedeploy"
1183 1183 self."six"
1184 1184 ];
1185 1185 src = fetchurl {
1186 1186 url = "https://files.pythonhosted.org/packages/ff/47/45c6f5a3cb8f5abf786fea98dbb8d02400a55768a9b623afb7df12346c61/PasteScript-3.2.0.tar.gz";
1187 1187 sha256 = "1b3jq7xh383nvrrlblk05m37345bv97xrhx77wshllba3h7mq3wv";
1188 1188 };
1189 1189 meta = {
1190 1190 license = [ pkgs.lib.licenses.mit ];
1191 1191 };
1192 1192 };
1193 1193 "pathlib2" = super.buildPythonPackage {
1194 1194 name = "pathlib2-2.3.5";
1195 1195 doCheck = false;
1196 1196 propagatedBuildInputs = [
1197 1197 self."six"
1198 1198 self."scandir"
1199 1199 ];
1200 1200 src = fetchurl {
1201 1201 url = "https://files.pythonhosted.org/packages/94/d8/65c86584e7e97ef824a1845c72bbe95d79f5b306364fa778a3c3e401b309/pathlib2-2.3.5.tar.gz";
1202 1202 sha256 = "0s4qa8c082fdkb17izh4mfgwrjd1n5pya18wvrbwqdvvb5xs9nbc";
1203 1203 };
1204 1204 meta = {
1205 1205 license = [ pkgs.lib.licenses.mit ];
1206 1206 };
1207 1207 };
1208 1208 "peppercorn" = super.buildPythonPackage {
1209 1209 name = "peppercorn-0.6";
1210 1210 doCheck = false;
1211 1211 src = fetchurl {
1212 1212 url = "https://files.pythonhosted.org/packages/e4/77/93085de7108cdf1a0b092ff443872a8f9442c736d7ddebdf2f27627935f4/peppercorn-0.6.tar.gz";
1213 1213 sha256 = "1ip4bfwcpwkq9hz2dai14k2cyabvwrnvcvrcmzxmqm04g8fnimwn";
1214 1214 };
1215 1215 meta = {
1216 1216 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1217 1217 };
1218 1218 };
1219 1219 "pexpect" = super.buildPythonPackage {
1220 1220 name = "pexpect-4.8.0";
1221 1221 doCheck = false;
1222 1222 propagatedBuildInputs = [
1223 1223 self."ptyprocess"
1224 1224 ];
1225 1225 src = fetchurl {
1226 1226 url = "https://files.pythonhosted.org/packages/e5/9b/ff402e0e930e70467a7178abb7c128709a30dfb22d8777c043e501bc1b10/pexpect-4.8.0.tar.gz";
1227 1227 sha256 = "032cg337h8awydgypz6f4wx848lw8dyrj4zy988x0lyib4ws8rgw";
1228 1228 };
1229 1229 meta = {
1230 1230 license = [ pkgs.lib.licenses.isc { fullName = "ISC License (ISCL)"; } ];
1231 1231 };
1232 1232 };
1233 1233 "pickleshare" = super.buildPythonPackage {
1234 1234 name = "pickleshare-0.7.5";
1235 1235 doCheck = false;
1236 1236 propagatedBuildInputs = [
1237 1237 self."pathlib2"
1238 1238 ];
1239 1239 src = fetchurl {
1240 1240 url = "https://files.pythonhosted.org/packages/d8/b6/df3c1c9b616e9c0edbc4fbab6ddd09df9535849c64ba51fcb6531c32d4d8/pickleshare-0.7.5.tar.gz";
1241 1241 sha256 = "1jmghg3c53yp1i8cm6pcrm280ayi8621rwyav9fac7awjr3kss47";
1242 1242 };
1243 1243 meta = {
1244 1244 license = [ pkgs.lib.licenses.mit ];
1245 1245 };
1246 1246 };
1247 1247 "plaster" = super.buildPythonPackage {
1248 1248 name = "plaster-1.0";
1249 1249 doCheck = false;
1250 1250 propagatedBuildInputs = [
1251 1251 self."setuptools"
1252 1252 ];
1253 1253 src = fetchurl {
1254 1254 url = "https://files.pythonhosted.org/packages/37/e1/56d04382d718d32751017d32f351214384e529b794084eee20bb52405563/plaster-1.0.tar.gz";
1255 1255 sha256 = "1hy8k0nv2mxq94y5aysk6hjk9ryb4bsd13g83m60hcyzxz3wflc3";
1256 1256 };
1257 1257 meta = {
1258 1258 license = [ pkgs.lib.licenses.mit ];
1259 1259 };
1260 1260 };
1261 1261 "plaster-pastedeploy" = super.buildPythonPackage {
1262 1262 name = "plaster-pastedeploy-0.7";
1263 1263 doCheck = false;
1264 1264 propagatedBuildInputs = [
1265 1265 self."pastedeploy"
1266 1266 self."plaster"
1267 1267 ];
1268 1268 src = fetchurl {
1269 1269 url = "https://files.pythonhosted.org/packages/99/69/2d3bc33091249266a1bd3cf24499e40ab31d54dffb4a7d76fe647950b98c/plaster_pastedeploy-0.7.tar.gz";
1270 1270 sha256 = "1zg7gcsvc1kzay1ry5p699rg2qavfsxqwl17mqxzr0gzw6j9679r";
1271 1271 };
1272 1272 meta = {
1273 1273 license = [ pkgs.lib.licenses.mit ];
1274 1274 };
1275 1275 };
1276 1276 "pluggy" = super.buildPythonPackage {
1277 1277 name = "pluggy-0.13.1";
1278 1278 doCheck = false;
1279 1279 propagatedBuildInputs = [
1280 1280 self."importlib-metadata"
1281 1281 ];
1282 1282 src = fetchurl {
1283 1283 url = "https://files.pythonhosted.org/packages/f8/04/7a8542bed4b16a65c2714bf76cf5a0b026157da7f75e87cc88774aa10b14/pluggy-0.13.1.tar.gz";
1284 1284 sha256 = "1c35qyhvy27q9ih9n899f3h4sdnpgq027dbiilly2qb5cvgarchm";
1285 1285 };
1286 1286 meta = {
1287 1287 license = [ pkgs.lib.licenses.mit ];
1288 1288 };
1289 1289 };
1290 1290 "premailer" = super.buildPythonPackage {
1291 1291 name = "premailer-3.6.1";
1292 1292 doCheck = false;
1293 1293 propagatedBuildInputs = [
1294 1294 self."lxml"
1295 1295 self."cssselect"
1296 1296 self."cssutils"
1297 1297 self."requests"
1298 1298 self."cachetools"
1299 1299 ];
1300 1300 src = fetchurl {
1301 1301 url = "https://files.pythonhosted.org/packages/62/da/2f43cdf9d3d79c80c4856a12389a1f257d65fe9ccc44bc6b4383c8a18e33/premailer-3.6.1.tar.gz";
1302 1302 sha256 = "08pshx7a110k4ll20x0xhpvyn3kkipkrbgxjjn7ncdxs54ihdhgw";
1303 1303 };
1304 1304 meta = {
1305 1305 license = [ pkgs.lib.licenses.psfl { fullName = "Python"; } ];
1306 1306 };
1307 1307 };
1308 1308 "prompt-toolkit" = super.buildPythonPackage {
1309 1309 name = "prompt-toolkit-1.0.18";
1310 1310 doCheck = false;
1311 1311 propagatedBuildInputs = [
1312 1312 self."six"
1313 1313 self."wcwidth"
1314 1314 ];
1315 1315 src = fetchurl {
1316 1316 url = "https://files.pythonhosted.org/packages/c5/64/c170e5b1913b540bf0c8ab7676b21fdd1d25b65ddeb10025c6ca43cccd4c/prompt_toolkit-1.0.18.tar.gz";
1317 1317 sha256 = "09h1153wgr5x2ny7ds0w2m81n3bb9j8hjb8sjfnrg506r01clkyx";
1318 1318 };
1319 1319 meta = {
1320 1320 license = [ pkgs.lib.licenses.bsdOriginal ];
1321 1321 };
1322 1322 };
1323 1323 "psutil" = super.buildPythonPackage {
1324 1324 name = "psutil-5.7.0";
1325 1325 doCheck = false;
1326 1326 src = fetchurl {
1327 1327 url = "https://files.pythonhosted.org/packages/c4/b8/3512f0e93e0db23a71d82485ba256071ebef99b227351f0f5540f744af41/psutil-5.7.0.tar.gz";
1328 1328 sha256 = "03jykdi3dgf1cdal9bv4fq9zjvzj9l9bs99gi5ar81sdl5nc2pk8";
1329 1329 };
1330 1330 meta = {
1331 1331 license = [ pkgs.lib.licenses.bsdOriginal ];
1332 1332 };
1333 1333 };
1334 1334 "psycopg2" = super.buildPythonPackage {
1335 1335 name = "psycopg2-2.8.4";
1336 1336 doCheck = false;
1337 1337 src = fetchurl {
1338 1338 url = "https://files.pythonhosted.org/packages/84/d7/6a93c99b5ba4d4d22daa3928b983cec66df4536ca50b22ce5dcac65e4e71/psycopg2-2.8.4.tar.gz";
1339 1339 sha256 = "1djvh98pi4hjd8rxbq8qzc63bg8v78k33yg6pl99wak61b6fb67q";
1340 1340 };
1341 1341 meta = {
1342 1342 license = [ pkgs.lib.licenses.zpl21 { fullName = "GNU Library or Lesser General Public License (LGPL)"; } { fullName = "LGPL with exceptions or ZPL"; } ];
1343 1343 };
1344 1344 };
1345 1345 "ptyprocess" = super.buildPythonPackage {
1346 1346 name = "ptyprocess-0.6.0";
1347 1347 doCheck = false;
1348 1348 src = fetchurl {
1349 1349 url = "https://files.pythonhosted.org/packages/7d/2d/e4b8733cf79b7309d84c9081a4ab558c89d8c89da5961bf4ddb050ca1ce0/ptyprocess-0.6.0.tar.gz";
1350 1350 sha256 = "1h4lcd3w5nrxnsk436ar7fwkiy5rfn5wj2xwy9l0r4mdqnf2jgwj";
1351 1351 };
1352 1352 meta = {
1353 1353 license = [ ];
1354 1354 };
1355 1355 };
1356 1356 "py" = super.buildPythonPackage {
1357 1357 name = "py-1.8.0";
1358 1358 doCheck = false;
1359 1359 src = fetchurl {
1360 1360 url = "https://files.pythonhosted.org/packages/f1/5a/87ca5909f400a2de1561f1648883af74345fe96349f34f737cdfc94eba8c/py-1.8.0.tar.gz";
1361 1361 sha256 = "0lsy1gajva083pzc7csj1cvbmminb7b4l6a0prdzyb3fd829nqyw";
1362 1362 };
1363 1363 meta = {
1364 1364 license = [ pkgs.lib.licenses.mit ];
1365 1365 };
1366 1366 };
1367 1367 "py-bcrypt" = super.buildPythonPackage {
1368 1368 name = "py-bcrypt-0.4";
1369 1369 doCheck = false;
1370 1370 src = fetchurl {
1371 1371 url = "https://files.pythonhosted.org/packages/68/b1/1c3068c5c4d2e35c48b38dcc865301ebfdf45f54507086ac65ced1fd3b3d/py-bcrypt-0.4.tar.gz";
1372 1372 sha256 = "0y6smdggwi5s72v6p1nn53dg6w05hna3d264cq6kas0lap73p8az";
1373 1373 };
1374 1374 meta = {
1375 1375 license = [ pkgs.lib.licenses.bsdOriginal ];
1376 1376 };
1377 1377 };
1378 1378 "py-gfm" = super.buildPythonPackage {
1379 1379 name = "py-gfm-0.1.4";
1380 1380 doCheck = false;
1381 1381 propagatedBuildInputs = [
1382 1382 self."setuptools"
1383 1383 self."markdown"
1384 1384 ];
1385 1385 src = fetchurl {
1386 1386 url = "https://files.pythonhosted.org/packages/06/ee/004a03a1d92bb386dae44f6dd087db541bc5093374f1637d4d4ae5596cc2/py-gfm-0.1.4.tar.gz";
1387 1387 sha256 = "0zip06g2isivx8fzgqd4n9qzsa22c25jas1rsb7m2rnjg72m0rzg";
1388 1388 };
1389 1389 meta = {
1390 1390 license = [ pkgs.lib.licenses.bsdOriginal ];
1391 1391 };
1392 1392 };
1393 1393 "pyasn1" = super.buildPythonPackage {
1394 1394 name = "pyasn1-0.4.8";
1395 1395 doCheck = false;
1396 1396 src = fetchurl {
1397 1397 url = "https://files.pythonhosted.org/packages/a4/db/fffec68299e6d7bad3d504147f9094830b704527a7fc098b721d38cc7fa7/pyasn1-0.4.8.tar.gz";
1398 1398 sha256 = "1fnhbi3rmk47l9851gbik0flfr64vs5j0hbqx24cafjap6gprxxf";
1399 1399 };
1400 1400 meta = {
1401 1401 license = [ pkgs.lib.licenses.bsdOriginal ];
1402 1402 };
1403 1403 };
1404 1404 "pyasn1-modules" = super.buildPythonPackage {
1405 1405 name = "pyasn1-modules-0.2.6";
1406 1406 doCheck = false;
1407 1407 propagatedBuildInputs = [
1408 1408 self."pyasn1"
1409 1409 ];
1410 1410 src = fetchurl {
1411 1411 url = "https://files.pythonhosted.org/packages/f1/a9/a1ef72a0e43feff643cf0130a08123dea76205e7a0dda37e3efb5f054a31/pyasn1-modules-0.2.6.tar.gz";
1412 1412 sha256 = "08hph9j1r018drnrny29l7dl2q0cin78csswrhwrh8jmq61pmha3";
1413 1413 };
1414 1414 meta = {
1415 1415 license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.bsd2 ];
1416 1416 };
1417 1417 };
1418 1418 "pycparser" = super.buildPythonPackage {
1419 1419 name = "pycparser-2.20";
1420 1420 doCheck = false;
1421 1421 src = fetchurl {
1422 1422 url = "https://files.pythonhosted.org/packages/0f/86/e19659527668d70be91d0369aeaa055b4eb396b0f387a4f92293a20035bd/pycparser-2.20.tar.gz";
1423 1423 sha256 = "1w0m3xvlrzq4lkbvd1ngfm8mdw64r1yxy6n7djlw6qj5d0km6ird";
1424 1424 };
1425 1425 meta = {
1426 1426 license = [ pkgs.lib.licenses.bsdOriginal ];
1427 1427 };
1428 1428 };
1429 1429 "pycrypto" = super.buildPythonPackage {
1430 1430 name = "pycrypto-2.6.1";
1431 1431 doCheck = false;
1432 1432 src = fetchurl {
1433 1433 url = "https://files.pythonhosted.org/packages/60/db/645aa9af249f059cc3a368b118de33889219e0362141e75d4eaf6f80f163/pycrypto-2.6.1.tar.gz";
1434 1434 sha256 = "0g0ayql5b9mkjam8hym6zyg6bv77lbh66rv1fyvgqb17kfc1xkpj";
1435 1435 };
1436 1436 meta = {
1437 1437 license = [ pkgs.lib.licenses.publicDomain ];
1438 1438 };
1439 1439 };
1440 1440 "pycurl" = super.buildPythonPackage {
1441 1441 name = "pycurl-7.43.0.3";
1442 1442 doCheck = false;
1443 1443 src = fetchurl {
1444 1444 url = "https://files.pythonhosted.org/packages/ac/b3/0f3979633b7890bab6098d84c84467030b807a1e2b31f5d30103af5a71ca/pycurl-7.43.0.3.tar.gz";
1445 1445 sha256 = "13nsvqhvnmnvfk75s8iynqsgszyv06cjp4drd3psi7zpbh63623g";
1446 1446 };
1447 1447 meta = {
1448 1448 license = [ pkgs.lib.licenses.mit { fullName = "LGPL/MIT"; } { fullName = "GNU Library or Lesser General Public License (LGPL)"; } ];
1449 1449 };
1450 1450 };
1451 1451 "pygments" = super.buildPythonPackage {
1452 1452 name = "pygments-2.4.2";
1453 1453 doCheck = false;
1454 1454 src = fetchurl {
1455 1455 url = "https://files.pythonhosted.org/packages/7e/ae/26808275fc76bf2832deb10d3a3ed3107bc4de01b85dcccbe525f2cd6d1e/Pygments-2.4.2.tar.gz";
1456 1456 sha256 = "15v2sqm5g12bqa0c7wikfh9ck2nl97ayizy1hpqhmws5gqalq748";
1457 1457 };
1458 1458 meta = {
1459 1459 license = [ pkgs.lib.licenses.bsdOriginal ];
1460 1460 };
1461 1461 };
1462 1462 "pymysql" = super.buildPythonPackage {
1463 1463 name = "pymysql-0.8.1";
1464 1464 doCheck = false;
1465 1465 src = fetchurl {
1466 1466 url = "https://files.pythonhosted.org/packages/44/39/6bcb83cae0095a31b6be4511707fdf2009d3e29903a55a0494d3a9a2fac0/PyMySQL-0.8.1.tar.gz";
1467 1467 sha256 = "0a96crz55bw4h6myh833skrli7b0ck89m3x673y2z2ryy7zrpq9l";
1468 1468 };
1469 1469 meta = {
1470 1470 license = [ pkgs.lib.licenses.mit ];
1471 1471 };
1472 1472 };
1473 1473 "pyotp" = super.buildPythonPackage {
1474 1474 name = "pyotp-2.3.0";
1475 1475 doCheck = false;
1476 1476 src = fetchurl {
1477 1477 url = "https://files.pythonhosted.org/packages/f7/15/395c4945ea6bc37e8811280bb675615cb4c2b2c1cd70bdc43329da91a386/pyotp-2.3.0.tar.gz";
1478 1478 sha256 = "18d13ikra1iq0xyfqfm72zhgwxi2qi9ps6z1a6zmqp4qrn57wlzw";
1479 1479 };
1480 1480 meta = {
1481 1481 license = [ pkgs.lib.licenses.mit ];
1482 1482 };
1483 1483 };
1484 1484 "pyparsing" = super.buildPythonPackage {
1485 1485 name = "pyparsing-2.4.7";
1486 1486 doCheck = false;
1487 1487 src = fetchurl {
1488 1488 url = "https://files.pythonhosted.org/packages/c1/47/dfc9c342c9842bbe0036c7f763d2d6686bcf5eb1808ba3e170afdb282210/pyparsing-2.4.7.tar.gz";
1489 1489 sha256 = "1hgc8qrbq1ymxbwfbjghv01fm3fbpjwpjwi0bcailxxzhf3yq0y2";
1490 1490 };
1491 1491 meta = {
1492 1492 license = [ pkgs.lib.licenses.mit ];
1493 1493 };
1494 1494 };
1495 1495 "pyramid" = super.buildPythonPackage {
1496 1496 name = "pyramid-1.10.4";
1497 1497 doCheck = false;
1498 1498 propagatedBuildInputs = [
1499 1499 self."hupper"
1500 1500 self."plaster"
1501 1501 self."plaster-pastedeploy"
1502 1502 self."setuptools"
1503 1503 self."translationstring"
1504 1504 self."venusian"
1505 1505 self."webob"
1506 1506 self."zope.deprecation"
1507 1507 self."zope.interface"
1508 1508 self."repoze.lru"
1509 1509 ];
1510 1510 src = fetchurl {
1511 1511 url = "https://files.pythonhosted.org/packages/c2/43/1ae701c9c6bb3a434358e678a5e72c96e8aa55cf4cb1d2fa2041b5dd38b7/pyramid-1.10.4.tar.gz";
1512 1512 sha256 = "0rkxs1ajycg2zh1c94xlmls56mx5m161sn8112skj0amza6cn36q";
1513 1513 };
1514 1514 meta = {
1515 1515 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1516 1516 };
1517 1517 };
1518 1518 "pyramid-debugtoolbar" = super.buildPythonPackage {
1519 1519 name = "pyramid-debugtoolbar-4.6.1";
1520 1520 doCheck = false;
1521 1521 propagatedBuildInputs = [
1522 1522 self."pyramid"
1523 1523 self."pyramid-mako"
1524 1524 self."repoze.lru"
1525 1525 self."pygments"
1526 1526 self."ipaddress"
1527 1527 ];
1528 1528 src = fetchurl {
1529 1529 url = "https://files.pythonhosted.org/packages/99/f6/b8603f82c18275be293921bc3a2184205056ca505747bf64ab8a0c08e124/pyramid_debugtoolbar-4.6.1.tar.gz";
1530 1530 sha256 = "185z7q8n959ga5331iczwra2iljwkidfx4qn6bbd7vm3rm4w6llv";
1531 1531 };
1532 1532 meta = {
1533 1533 license = [ { fullName = "Repoze Public License"; } pkgs.lib.licenses.bsdOriginal ];
1534 1534 };
1535 1535 };
1536 1536 "pyramid-jinja2" = super.buildPythonPackage {
1537 1537 name = "pyramid-jinja2-2.7";
1538 1538 doCheck = false;
1539 1539 propagatedBuildInputs = [
1540 1540 self."pyramid"
1541 1541 self."zope.deprecation"
1542 1542 self."jinja2"
1543 1543 self."markupsafe"
1544 1544 ];
1545 1545 src = fetchurl {
1546 1546 url = "https://files.pythonhosted.org/packages/d8/80/d60a7233823de22ce77bd864a8a83736a1fe8b49884b08303a2e68b2c853/pyramid_jinja2-2.7.tar.gz";
1547 1547 sha256 = "1sz5s0pp5jqhf4w22w9527yz8hgdi4mhr6apd6vw1gm5clghh8aw";
1548 1548 };
1549 1549 meta = {
1550 1550 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1551 1551 };
1552 1552 };
1553 1553 "pyramid-apispec" = super.buildPythonPackage {
1554 1554 name = "pyramid-apispec-0.3.2";
1555 1555 doCheck = false;
1556 1556 propagatedBuildInputs = [
1557 1557 self."apispec"
1558 1558 ];
1559 1559 src = fetchurl {
1560 1560 url = "https://files.pythonhosted.org/packages/2a/30/1dea5d81ea635449572ba60ec3148310d75ae4530c3c695f54b0991bb8c7/pyramid_apispec-0.3.2.tar.gz";
1561 1561 sha256 = "0ffrcqp9dkykivhfcq0v9lgy6w0qhwl6x78925vfjmayly9r8da0";
1562 1562 };
1563 1563 meta = {
1564 1564 license = [ pkgs.lib.licenses.bsdOriginal ];
1565 1565 };
1566 1566 };
1567 1567 "pyramid-mailer" = super.buildPythonPackage {
1568 1568 name = "pyramid-mailer-0.15.1";
1569 1569 doCheck = false;
1570 1570 propagatedBuildInputs = [
1571 1571 self."pyramid"
1572 1572 self."repoze.sendmail"
1573 1573 self."transaction"
1574 1574 ];
1575 1575 src = fetchurl {
1576 1576 url = "https://files.pythonhosted.org/packages/a0/f2/6febf5459dff4d7e653314d575469ad2e11b9d2af2c3606360e1c67202f2/pyramid_mailer-0.15.1.tar.gz";
1577 1577 sha256 = "16vg8jb203jgb7b0hd6wllfqvp542qh2ry1gjai2m6qpv5agy2pc";
1578 1578 };
1579 1579 meta = {
1580 1580 license = [ pkgs.lib.licenses.bsdOriginal ];
1581 1581 };
1582 1582 };
1583 1583 "pyramid-mako" = super.buildPythonPackage {
1584 1584 name = "pyramid-mako-1.1.0";
1585 1585 doCheck = false;
1586 1586 propagatedBuildInputs = [
1587 1587 self."pyramid"
1588 1588 self."mako"
1589 1589 ];
1590 1590 src = fetchurl {
1591 1591 url = "https://files.pythonhosted.org/packages/63/7b/5e2af68f675071a6bad148c1c393928f0ef5fcd94e95cbf53b89d6471a83/pyramid_mako-1.1.0.tar.gz";
1592 1592 sha256 = "1qj0m091mnii86j2q1d82yir22nha361rvhclvg3s70z8iiwhrh0";
1593 1593 };
1594 1594 meta = {
1595 1595 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1596 1596 };
1597 1597 };
1598 1598 "pysqlite" = super.buildPythonPackage {
1599 1599 name = "pysqlite-2.8.3";
1600 1600 doCheck = false;
1601 1601 src = fetchurl {
1602 1602 url = "https://files.pythonhosted.org/packages/42/02/981b6703e3c83c5b25a829c6e77aad059f9481b0bbacb47e6e8ca12bd731/pysqlite-2.8.3.tar.gz";
1603 1603 sha256 = "1424gwq9sil2ffmnizk60q36vydkv8rxs6m7xs987kz8cdc37lqp";
1604 1604 };
1605 1605 meta = {
1606 1606 license = [ { fullName = "zlib/libpng License"; } { fullName = "zlib/libpng license"; } ];
1607 1607 };
1608 1608 };
1609 1609 "pytest" = super.buildPythonPackage {
1610 1610 name = "pytest-4.6.5";
1611 1611 doCheck = false;
1612 1612 propagatedBuildInputs = [
1613 1613 self."py"
1614 1614 self."six"
1615 1615 self."packaging"
1616 1616 self."attrs"
1617 1617 self."atomicwrites"
1618 1618 self."pluggy"
1619 1619 self."importlib-metadata"
1620 1620 self."wcwidth"
1621 1621 self."funcsigs"
1622 1622 self."pathlib2"
1623 1623 self."more-itertools"
1624 1624 ];
1625 1625 src = fetchurl {
1626 1626 url = "https://files.pythonhosted.org/packages/2a/c6/1d1f32f6a5009900521b12e6560fb6b7245b0d4bc3fb771acd63d10e30e1/pytest-4.6.5.tar.gz";
1627 1627 sha256 = "0iykwwfp4h181nd7rsihh2120b0rkawlw7rvbl19sgfspncr3hwg";
1628 1628 };
1629 1629 meta = {
1630 1630 license = [ pkgs.lib.licenses.mit ];
1631 1631 };
1632 1632 };
1633 1633 "pytest-cov" = super.buildPythonPackage {
1634 1634 name = "pytest-cov-2.7.1";
1635 1635 doCheck = false;
1636 1636 propagatedBuildInputs = [
1637 1637 self."pytest"
1638 1638 self."coverage"
1639 1639 ];
1640 1640 src = fetchurl {
1641 1641 url = "https://files.pythonhosted.org/packages/bb/0f/3db7ff86801883b21d5353b258c994b1b8e2abbc804e2273b8d0fd19004b/pytest-cov-2.7.1.tar.gz";
1642 1642 sha256 = "0filvmmyqm715azsl09ql8hy2x7h286n6d8z5x42a1wpvvys83p0";
1643 1643 };
1644 1644 meta = {
1645 1645 license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.mit ];
1646 1646 };
1647 1647 };
1648 1648 "pytest-profiling" = super.buildPythonPackage {
1649 1649 name = "pytest-profiling-1.7.0";
1650 1650 doCheck = false;
1651 1651 propagatedBuildInputs = [
1652 1652 self."six"
1653 1653 self."pytest"
1654 1654 self."gprof2dot"
1655 1655 ];
1656 1656 src = fetchurl {
1657 1657 url = "https://files.pythonhosted.org/packages/39/70/22a4b33739f07f1732a63e33bbfbf68e0fa58cfba9d200e76d01921eddbf/pytest-profiling-1.7.0.tar.gz";
1658 1658 sha256 = "0abz9gi26jpcfdzgsvwad91555lpgdc8kbymicmms8k2fqa8z4wk";
1659 1659 };
1660 1660 meta = {
1661 1661 license = [ pkgs.lib.licenses.mit ];
1662 1662 };
1663 1663 };
1664 1664 "pytest-runner" = super.buildPythonPackage {
1665 1665 name = "pytest-runner-5.1";
1666 1666 doCheck = false;
1667 1667 src = fetchurl {
1668 1668 url = "https://files.pythonhosted.org/packages/d9/6d/4b41a74b31720e25abd4799be72d54811da4b4d0233e38b75864dcc1f7ad/pytest-runner-5.1.tar.gz";
1669 1669 sha256 = "0ykfcnpp8c22winj63qzc07l5axwlc9ikl8vn05sc32gv3417815";
1670 1670 };
1671 1671 meta = {
1672 1672 license = [ pkgs.lib.licenses.mit ];
1673 1673 };
1674 1674 };
1675 1675 "pytest-sugar" = super.buildPythonPackage {
1676 1676 name = "pytest-sugar-0.9.2";
1677 1677 doCheck = false;
1678 1678 propagatedBuildInputs = [
1679 1679 self."pytest"
1680 1680 self."termcolor"
1681 1681 self."packaging"
1682 1682 ];
1683 1683 src = fetchurl {
1684 1684 url = "https://files.pythonhosted.org/packages/55/59/f02f78d1c80f7e03e23177f60624c8106d4f23d124c921df103f65692464/pytest-sugar-0.9.2.tar.gz";
1685 1685 sha256 = "1asq7yc4g8bx2sn7yy974mhc9ywvaihasjab4inkirdwn9s7mn7w";
1686 1686 };
1687 1687 meta = {
1688 1688 license = [ pkgs.lib.licenses.bsdOriginal ];
1689 1689 };
1690 1690 };
1691 1691 "pytest-timeout" = super.buildPythonPackage {
1692 1692 name = "pytest-timeout-1.3.3";
1693 1693 doCheck = false;
1694 1694 propagatedBuildInputs = [
1695 1695 self."pytest"
1696 1696 ];
1697 1697 src = fetchurl {
1698 1698 url = "https://files.pythonhosted.org/packages/13/48/7a166eaa29c1dca6cc253e3ba5773ff2e4aa4f567c1ea3905808e95ac5c1/pytest-timeout-1.3.3.tar.gz";
1699 1699 sha256 = "1cczcjhw4xx5sjkhxlhc5c1bkr7x6fcyx12wrnvwfckshdvblc2a";
1700 1700 };
1701 1701 meta = {
1702 1702 license = [ pkgs.lib.licenses.mit { fullName = "DFSG approved"; } ];
1703 1703 };
1704 1704 };
1705 1705 "python-dateutil" = super.buildPythonPackage {
1706 1706 name = "python-dateutil-2.8.1";
1707 1707 doCheck = false;
1708 1708 propagatedBuildInputs = [
1709 1709 self."six"
1710 1710 ];
1711 1711 src = fetchurl {
1712 1712 url = "https://files.pythonhosted.org/packages/be/ed/5bbc91f03fa4c839c4c7360375da77f9659af5f7086b7a7bdda65771c8e0/python-dateutil-2.8.1.tar.gz";
1713 1713 sha256 = "0g42w7k5007iv9dam6gnja2ry8ydwirh99mgdll35s12pyfzxsvk";
1714 1714 };
1715 1715 meta = {
1716 1716 license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.asl20 { fullName = "Dual License"; } ];
1717 1717 };
1718 1718 };
1719 1719 "python-editor" = super.buildPythonPackage {
1720 1720 name = "python-editor-1.0.4";
1721 1721 doCheck = false;
1722 1722 src = fetchurl {
1723 1723 url = "https://files.pythonhosted.org/packages/0a/85/78f4a216d28343a67b7397c99825cff336330893f00601443f7c7b2f2234/python-editor-1.0.4.tar.gz";
1724 1724 sha256 = "0yrjh8w72ivqxi4i7xsg5b1vz15x8fg51xra7c3bgfyxqnyadzai";
1725 1725 };
1726 1726 meta = {
1727 1727 license = [ pkgs.lib.licenses.asl20 { fullName = "Apache"; } ];
1728 1728 };
1729 1729 };
1730 1730 "python-ldap" = super.buildPythonPackage {
1731 1731 name = "python-ldap-3.2.0";
1732 1732 doCheck = false;
1733 1733 propagatedBuildInputs = [
1734 1734 self."pyasn1"
1735 1735 self."pyasn1-modules"
1736 1736 ];
1737 1737 src = fetchurl {
1738 1738 url = "https://files.pythonhosted.org/packages/ea/93/596f875e003c770447f4b99267820a0c769dd2dc3ae3ed19afe460fcbad0/python-ldap-3.2.0.tar.gz";
1739 1739 sha256 = "13nvrhp85yr0jyxixcjj012iw8l9wynxxlykm9j3alss6waln73x";
1740 1740 };
1741 1741 meta = {
1742 1742 license = [ pkgs.lib.licenses.psfl ];
1743 1743 };
1744 1744 };
1745 1745 "python-memcached" = super.buildPythonPackage {
1746 1746 name = "python-memcached-1.59";
1747 1747 doCheck = false;
1748 1748 propagatedBuildInputs = [
1749 1749 self."six"
1750 1750 ];
1751 1751 src = fetchurl {
1752 1752 url = "https://files.pythonhosted.org/packages/90/59/5faf6e3cd8a568dd4f737ddae4f2e54204fd8c51f90bf8df99aca6c22318/python-memcached-1.59.tar.gz";
1753 1753 sha256 = "0kvyapavbirk2x3n1jx4yb9nyigrj1s3x15nm3qhpvhkpqvqdqm2";
1754 1754 };
1755 1755 meta = {
1756 1756 license = [ pkgs.lib.licenses.psfl ];
1757 1757 };
1758 1758 };
1759 1759 "python-pam" = super.buildPythonPackage {
1760 1760 name = "python-pam-1.8.4";
1761 1761 doCheck = false;
1762 1762 src = fetchurl {
1763 1763 url = "https://files.pythonhosted.org/packages/01/16/544d01cae9f28e0292dbd092b6b8b0bf222b528f362ee768a5bed2140111/python-pam-1.8.4.tar.gz";
1764 1764 sha256 = "16whhc0vr7gxsbzvsnq65nq8fs3wwmx755cavm8kkczdkz4djmn8";
1765 1765 };
1766 1766 meta = {
1767 1767 license = [ { fullName = "License :: OSI Approved :: MIT License"; } pkgs.lib.licenses.mit ];
1768 1768 };
1769 1769 };
1770 1770 "python-saml" = super.buildPythonPackage {
1771 1771 name = "python-saml-2.4.2";
1772 1772 doCheck = false;
1773 1773 propagatedBuildInputs = [
1774 1774 self."dm.xmlsec.binding"
1775 1775 self."isodate"
1776 1776 self."defusedxml"
1777 1777 ];
1778 1778 src = fetchurl {
1779 1779 url = "https://files.pythonhosted.org/packages/79/a8/a6611017e0883102fd5e2b73c9d90691b8134e38247c04ee1531d3dc647c/python-saml-2.4.2.tar.gz";
1780 1780 sha256 = "0dls4hwvf13yg7x5yfjrghbywg8g38vn5vr0rsf70hli3ydbfm43";
1781 1781 };
1782 1782 meta = {
1783 1783 license = [ pkgs.lib.licenses.mit ];
1784 1784 };
1785 1785 };
1786 1786 "pytz" = super.buildPythonPackage {
1787 1787 name = "pytz-2019.3";
1788 1788 doCheck = false;
1789 1789 src = fetchurl {
1790 1790 url = "https://files.pythonhosted.org/packages/82/c3/534ddba230bd4fbbd3b7a3d35f3341d014cca213f369a9940925e7e5f691/pytz-2019.3.tar.gz";
1791 1791 sha256 = "1ghrk1wg45d3nymj7bf4zj03n3bh64xmczhk4pfi577hdkdhcb5h";
1792 1792 };
1793 1793 meta = {
1794 1794 license = [ pkgs.lib.licenses.mit ];
1795 1795 };
1796 1796 };
1797 1797 "pyzmq" = super.buildPythonPackage {
1798 1798 name = "pyzmq-14.6.0";
1799 1799 doCheck = false;
1800 1800 src = fetchurl {
1801 1801 url = "https://files.pythonhosted.org/packages/8a/3b/5463d5a9d712cd8bbdac335daece0d69f6a6792da4e3dd89956c0db4e4e6/pyzmq-14.6.0.tar.gz";
1802 1802 sha256 = "1frmbjykvhmdg64g7sn20c9fpamrsfxwci1nhhg8q7jgz5pq0ikp";
1803 1803 };
1804 1804 meta = {
1805 1805 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "LGPL+BSD"; } { fullName = "GNU Library or Lesser General Public License (LGPL)"; } ];
1806 1806 };
1807 1807 };
1808 1808 "PyYAML" = super.buildPythonPackage {
1809 1809 name = "PyYAML-5.3.1";
1810 1810 doCheck = false;
1811 1811 src = fetchurl {
1812 1812 url = "https://files.pythonhosted.org/packages/64/c2/b80047c7ac2478f9501676c988a5411ed5572f35d1beff9cae07d321512c/PyYAML-5.3.1.tar.gz";
1813 1813 sha256 = "0pb4zvkfxfijkpgd1b86xjsqql97ssf1knbd1v53wkg1qm9cgsmq";
1814 1814 };
1815 1815 meta = {
1816 1816 license = [ pkgs.lib.licenses.mit ];
1817 1817 };
1818 1818 };
1819 1819 "regex" = super.buildPythonPackage {
1820 1820 name = "regex-2020.9.27";
1821 1821 doCheck = false;
1822 1822 src = fetchurl {
1823 1823 url = "https://files.pythonhosted.org/packages/93/8c/17f45cdfb39b13d4b5f909e4b4c2917abcbdef9c0036919a0399769148cf/regex-2020.9.27.tar.gz";
1824 1824 sha256 = "179ngfzwbsjvn5vhyzdahvmg0f7acahkwwy9bpjy1pv08bm2mwx6";
1825 1825 };
1826 1826 meta = {
1827 1827 license = [ pkgs.lib.licenses.psfl ];
1828 1828 };
1829 1829 };
1830 1830 "redis" = super.buildPythonPackage {
1831 1831 name = "redis-3.4.1";
1832 1832 doCheck = false;
1833 1833 src = fetchurl {
1834 1834 url = "https://files.pythonhosted.org/packages/ef/2e/2c0f59891db7db087a7eeaa79bc7c7f2c039e71a2b5b0a41391e9d462926/redis-3.4.1.tar.gz";
1835 1835 sha256 = "07yaj0j9fs7xdkg5bg926fa990khyigjbp31si8ai20vj8sv7kqd";
1836 1836 };
1837 1837 meta = {
1838 1838 license = [ pkgs.lib.licenses.mit ];
1839 1839 };
1840 1840 };
1841 1841 "repoze.lru" = super.buildPythonPackage {
1842 1842 name = "repoze.lru-0.7";
1843 1843 doCheck = false;
1844 1844 src = fetchurl {
1845 1845 url = "https://files.pythonhosted.org/packages/12/bc/595a77c4b5e204847fdf19268314ef59c85193a9dc9f83630fc459c0fee5/repoze.lru-0.7.tar.gz";
1846 1846 sha256 = "0xzz1aw2smy8hdszrq8yhnklx6w1r1mf55061kalw3iq35gafa84";
1847 1847 };
1848 1848 meta = {
1849 1849 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1850 1850 };
1851 1851 };
1852 1852 "repoze.sendmail" = super.buildPythonPackage {
1853 1853 name = "repoze.sendmail-4.4.1";
1854 1854 doCheck = false;
1855 1855 propagatedBuildInputs = [
1856 1856 self."setuptools"
1857 1857 self."zope.interface"
1858 1858 self."transaction"
1859 1859 ];
1860 1860 src = fetchurl {
1861 1861 url = "https://files.pythonhosted.org/packages/12/4e/8ef1fd5c42765d712427b9c391419a77bd48877886d2cbc5e9f23c8cad9b/repoze.sendmail-4.4.1.tar.gz";
1862 1862 sha256 = "096ln02jr2afk7ab9j2czxqv2ryqq7m86ah572nqplx52iws73ks";
1863 1863 };
1864 1864 meta = {
1865 1865 license = [ pkgs.lib.licenses.zpl21 ];
1866 1866 };
1867 1867 };
1868 1868 "requests" = super.buildPythonPackage {
1869 1869 name = "requests-2.22.0";
1870 1870 doCheck = false;
1871 1871 propagatedBuildInputs = [
1872 1872 self."chardet"
1873 1873 self."idna"
1874 1874 self."urllib3"
1875 1875 self."certifi"
1876 1876 ];
1877 1877 src = fetchurl {
1878 1878 url = "https://files.pythonhosted.org/packages/01/62/ddcf76d1d19885e8579acb1b1df26a852b03472c0e46d2b959a714c90608/requests-2.22.0.tar.gz";
1879 1879 sha256 = "1d5ybh11jr5sm7xp6mz8fyc7vrp4syifds91m7sj60xalal0gq0i";
1880 1880 };
1881 1881 meta = {
1882 1882 license = [ pkgs.lib.licenses.asl20 ];
1883 1883 };
1884 1884 };
1885 1885 "rhodecode-enterprise-ce" = super.buildPythonPackage {
1886 name = "rhodecode-enterprise-ce-4.23.0";
1886 name = "rhodecode-enterprise-ce-4.23.2";
1887 1887 buildInputs = [
1888 1888 self."pytest"
1889 1889 self."py"
1890 1890 self."pytest-cov"
1891 1891 self."pytest-sugar"
1892 1892 self."pytest-runner"
1893 1893 self."pytest-profiling"
1894 1894 self."pytest-timeout"
1895 1895 self."gprof2dot"
1896 1896 self."mock"
1897 1897 self."cov-core"
1898 1898 self."coverage"
1899 1899 self."webtest"
1900 1900 self."beautifulsoup4"
1901 1901 self."configobj"
1902 1902 ];
1903 1903 doCheck = true;
1904 1904 propagatedBuildInputs = [
1905 1905 self."amqp"
1906 1906 self."babel"
1907 1907 self."beaker"
1908 1908 self."bleach"
1909 1909 self."celery"
1910 1910 self."channelstream"
1911 1911 self."click"
1912 1912 self."colander"
1913 1913 self."configobj"
1914 1914 self."cssselect"
1915 1915 self."cryptography"
1916 1916 self."decorator"
1917 1917 self."deform"
1918 1918 self."docutils"
1919 1919 self."dogpile.cache"
1920 1920 self."dogpile.core"
1921 1921 self."formencode"
1922 1922 self."future"
1923 1923 self."futures"
1924 1924 self."infrae.cache"
1925 1925 self."iso8601"
1926 1926 self."itsdangerous"
1927 1927 self."kombu"
1928 1928 self."lxml"
1929 1929 self."mako"
1930 1930 self."markdown"
1931 1931 self."markupsafe"
1932 1932 self."msgpack-python"
1933 1933 self."pyotp"
1934 1934 self."packaging"
1935 1935 self."pathlib2"
1936 1936 self."paste"
1937 1937 self."pastedeploy"
1938 1938 self."pastescript"
1939 1939 self."peppercorn"
1940 1940 self."premailer"
1941 1941 self."psutil"
1942 1942 self."py-bcrypt"
1943 1943 self."pycurl"
1944 1944 self."pycrypto"
1945 1945 self."pygments"
1946 1946 self."pyparsing"
1947 1947 self."pyramid-debugtoolbar"
1948 1948 self."pyramid-mako"
1949 1949 self."pyramid"
1950 1950 self."pyramid-mailer"
1951 1951 self."python-dateutil"
1952 1952 self."python-ldap"
1953 1953 self."python-memcached"
1954 1954 self."python-pam"
1955 1955 self."python-saml"
1956 1956 self."pytz"
1957 1957 self."tzlocal"
1958 1958 self."pyzmq"
1959 1959 self."py-gfm"
1960 1960 self."regex"
1961 1961 self."redis"
1962 1962 self."repoze.lru"
1963 1963 self."requests"
1964 1964 self."routes"
1965 1965 self."simplejson"
1966 1966 self."six"
1967 1967 self."sqlalchemy"
1968 1968 self."sshpubkeys"
1969 1969 self."subprocess32"
1970 1970 self."supervisor"
1971 1971 self."translationstring"
1972 1972 self."urllib3"
1973 1973 self."urlobject"
1974 1974 self."venusian"
1975 1975 self."weberror"
1976 1976 self."webhelpers2"
1977 1977 self."webob"
1978 1978 self."whoosh"
1979 1979 self."wsgiref"
1980 1980 self."zope.cachedescriptors"
1981 1981 self."zope.deprecation"
1982 1982 self."zope.event"
1983 1983 self."zope.interface"
1984 1984 self."mysql-python"
1985 1985 self."pymysql"
1986 1986 self."pysqlite"
1987 1987 self."psycopg2"
1988 1988 self."nbconvert"
1989 1989 self."nbformat"
1990 1990 self."jupyter-client"
1991 1991 self."jupyter-core"
1992 1992 self."alembic"
1993 1993 self."invoke"
1994 1994 self."bumpversion"
1995 1995 self."gevent"
1996 1996 self."greenlet"
1997 1997 self."gunicorn"
1998 1998 self."waitress"
1999 1999 self."ipdb"
2000 2000 self."ipython"
2001 2001 self."rhodecode-tools"
2002 2002 self."appenlight-client"
2003 2003 self."pytest"
2004 2004 self."py"
2005 2005 self."pytest-cov"
2006 2006 self."pytest-sugar"
2007 2007 self."pytest-runner"
2008 2008 self."pytest-profiling"
2009 2009 self."pytest-timeout"
2010 2010 self."gprof2dot"
2011 2011 self."mock"
2012 2012 self."cov-core"
2013 2013 self."coverage"
2014 2014 self."webtest"
2015 2015 self."beautifulsoup4"
2016 2016 ];
2017 2017 src = ./.;
2018 2018 meta = {
2019 2019 license = [ { fullName = "Affero GNU General Public License v3 or later (AGPLv3+)"; } { fullName = "AGPLv3, and Commercial License"; } ];
2020 2020 };
2021 2021 };
2022 2022 "rhodecode-tools" = super.buildPythonPackage {
2023 2023 name = "rhodecode-tools-1.4.0";
2024 2024 doCheck = false;
2025 2025 propagatedBuildInputs = [
2026 2026 self."click"
2027 2027 self."future"
2028 2028 self."six"
2029 2029 self."mako"
2030 2030 self."markupsafe"
2031 2031 self."requests"
2032 2032 self."urllib3"
2033 2033 self."whoosh"
2034 2034 self."elasticsearch"
2035 2035 self."elasticsearch-dsl"
2036 2036 self."elasticsearch2"
2037 2037 self."elasticsearch1-dsl"
2038 2038 ];
2039 2039 src = fetchurl {
2040 2040 url = "https://code.rhodecode.com/rhodecode-tools-ce/artifacts/download/0-ed54e749-2ef5-4bc7-ae7f-7900e3c2aa15.tar.gz?sha256=76f024bad3a1e55fdb3d64f13f5b77ff21a12fee699918de2110fe21effd5a3a";
2041 2041 sha256 = "0fjszppj3zhh47g1i6b9xqps28gzfxdkzwb47pdmzrd1sfx29w3n";
2042 2042 };
2043 2043 meta = {
2044 2044 license = [ { fullName = "Apache 2.0 and Proprietary"; } ];
2045 2045 };
2046 2046 };
2047 2047 "routes" = super.buildPythonPackage {
2048 2048 name = "routes-2.4.1";
2049 2049 doCheck = false;
2050 2050 propagatedBuildInputs = [
2051 2051 self."six"
2052 2052 self."repoze.lru"
2053 2053 ];
2054 2054 src = fetchurl {
2055 2055 url = "https://files.pythonhosted.org/packages/33/38/ea827837e68d9c7dde4cff7ec122a93c319f0effc08ce92a17095576603f/Routes-2.4.1.tar.gz";
2056 2056 sha256 = "1zamff3m0kc4vyfniyhxpkkcqv1rrgnmh37ykxv34nna1ws47vi6";
2057 2057 };
2058 2058 meta = {
2059 2059 license = [ pkgs.lib.licenses.mit ];
2060 2060 };
2061 2061 };
2062 2062 "scandir" = super.buildPythonPackage {
2063 2063 name = "scandir-1.10.0";
2064 2064 doCheck = false;
2065 2065 src = fetchurl {
2066 2066 url = "https://files.pythonhosted.org/packages/df/f5/9c052db7bd54d0cbf1bc0bb6554362bba1012d03e5888950a4f5c5dadc4e/scandir-1.10.0.tar.gz";
2067 2067 sha256 = "1bkqwmf056pkchf05ywbnf659wqlp6lljcdb0y88wr9f0vv32ijd";
2068 2068 };
2069 2069 meta = {
2070 2070 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "New BSD License"; } ];
2071 2071 };
2072 2072 };
2073 2073 "setproctitle" = super.buildPythonPackage {
2074 2074 name = "setproctitle-1.1.10";
2075 2075 doCheck = false;
2076 2076 src = fetchurl {
2077 2077 url = "https://files.pythonhosted.org/packages/5a/0d/dc0d2234aacba6cf1a729964383e3452c52096dc695581248b548786f2b3/setproctitle-1.1.10.tar.gz";
2078 2078 sha256 = "163kplw9dcrw0lffq1bvli5yws3rngpnvrxrzdw89pbphjjvg0v2";
2079 2079 };
2080 2080 meta = {
2081 2081 license = [ pkgs.lib.licenses.bsdOriginal ];
2082 2082 };
2083 2083 };
2084 2084 "setuptools" = super.buildPythonPackage {
2085 2085 name = "setuptools-44.1.0";
2086 2086 doCheck = false;
2087 2087 src = fetchurl {
2088 2088 url = "https://files.pythonhosted.org/packages/ed/7b/bbf89ca71e722b7f9464ebffe4b5ee20a9e5c9a555a56e2d3914bb9119a6/setuptools-44.1.0.zip";
2089 2089 sha256 = "1jja896zvd1ppccnjbhkgagxbwchgq6vfamp6qn1hvywq6q9cjkr";
2090 2090 };
2091 2091 meta = {
2092 2092 license = [ pkgs.lib.licenses.mit ];
2093 2093 };
2094 2094 };
2095 2095 "simplegeneric" = super.buildPythonPackage {
2096 2096 name = "simplegeneric-0.8.1";
2097 2097 doCheck = false;
2098 2098 src = fetchurl {
2099 2099 url = "https://files.pythonhosted.org/packages/3d/57/4d9c9e3ae9a255cd4e1106bb57e24056d3d0709fc01b2e3e345898e49d5b/simplegeneric-0.8.1.zip";
2100 2100 sha256 = "0wwi1c6md4vkbcsfsf8dklf3vr4mcdj4mpxkanwgb6jb1432x5yw";
2101 2101 };
2102 2102 meta = {
2103 2103 license = [ pkgs.lib.licenses.zpl21 ];
2104 2104 };
2105 2105 };
2106 2106 "simplejson" = super.buildPythonPackage {
2107 2107 name = "simplejson-3.16.0";
2108 2108 doCheck = false;
2109 2109 src = fetchurl {
2110 2110 url = "https://files.pythonhosted.org/packages/e3/24/c35fb1c1c315fc0fffe61ea00d3f88e85469004713dab488dee4f35b0aff/simplejson-3.16.0.tar.gz";
2111 2111 sha256 = "19cws1syk8jzq2pw43878dv6fjkb0ifvjpx0i9aajix6kc9jkwxi";
2112 2112 };
2113 2113 meta = {
2114 2114 license = [ { fullName = "Academic Free License (AFL)"; } pkgs.lib.licenses.mit ];
2115 2115 };
2116 2116 };
2117 2117 "six" = super.buildPythonPackage {
2118 2118 name = "six-1.11.0";
2119 2119 doCheck = false;
2120 2120 src = fetchurl {
2121 2121 url = "https://files.pythonhosted.org/packages/16/d8/bc6316cf98419719bd59c91742194c111b6f2e85abac88e496adefaf7afe/six-1.11.0.tar.gz";
2122 2122 sha256 = "1scqzwc51c875z23phj48gircqjgnn3af8zy2izjwmnlxrxsgs3h";
2123 2123 };
2124 2124 meta = {
2125 2125 license = [ pkgs.lib.licenses.mit ];
2126 2126 };
2127 2127 };
2128 2128 "sqlalchemy" = super.buildPythonPackage {
2129 2129 name = "sqlalchemy-1.3.15";
2130 2130 doCheck = false;
2131 2131 src = fetchurl {
2132 2132 url = "https://files.pythonhosted.org/packages/8c/30/4134e726dd5ed13728ff814fa91fc01c447ad8700504653fe99d91fdd34b/SQLAlchemy-1.3.15.tar.gz";
2133 2133 sha256 = "0iglkvymfp35zm5pxy5kzqvcv96kkas0chqdx7xpla86sspa9k64";
2134 2134 };
2135 2135 meta = {
2136 2136 license = [ pkgs.lib.licenses.mit ];
2137 2137 };
2138 2138 };
2139 2139 "sshpubkeys" = super.buildPythonPackage {
2140 2140 name = "sshpubkeys-3.1.0";
2141 2141 doCheck = false;
2142 2142 propagatedBuildInputs = [
2143 2143 self."cryptography"
2144 2144 self."ecdsa"
2145 2145 ];
2146 2146 src = fetchurl {
2147 2147 url = "https://files.pythonhosted.org/packages/00/23/f7508a12007c96861c3da811992f14283d79c819d71a217b3e12d5196649/sshpubkeys-3.1.0.tar.gz";
2148 2148 sha256 = "105g2li04nm1hb15a2y6hm9m9k7fbrkd5l3gy12w3kgcmsf3k25k";
2149 2149 };
2150 2150 meta = {
2151 2151 license = [ pkgs.lib.licenses.bsdOriginal ];
2152 2152 };
2153 2153 };
2154 2154 "subprocess32" = super.buildPythonPackage {
2155 2155 name = "subprocess32-3.5.4";
2156 2156 doCheck = false;
2157 2157 src = fetchurl {
2158 2158 url = "https://files.pythonhosted.org/packages/32/c8/564be4d12629b912ea431f1a50eb8b3b9d00f1a0b1ceff17f266be190007/subprocess32-3.5.4.tar.gz";
2159 2159 sha256 = "17f7mvwx2271s1wrl0qac3wjqqnrqag866zs3qc8v5wp0k43fagb";
2160 2160 };
2161 2161 meta = {
2162 2162 license = [ pkgs.lib.licenses.psfl ];
2163 2163 };
2164 2164 };
2165 2165 "supervisor" = super.buildPythonPackage {
2166 2166 name = "supervisor-4.1.0";
2167 2167 doCheck = false;
2168 2168 src = fetchurl {
2169 2169 url = "https://files.pythonhosted.org/packages/de/87/ee1ad8fa533a4b5f2c7623f4a2b585d3c1947af7bed8e65bc7772274320e/supervisor-4.1.0.tar.gz";
2170 2170 sha256 = "10q36sa1jqljyyyl7cif52akpygl5kmlqq9x91hmx53f8zh6zj1d";
2171 2171 };
2172 2172 meta = {
2173 2173 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
2174 2174 };
2175 2175 };
2176 2176 "tempita" = super.buildPythonPackage {
2177 2177 name = "tempita-0.5.2";
2178 2178 doCheck = false;
2179 2179 src = fetchurl {
2180 2180 url = "https://files.pythonhosted.org/packages/56/c8/8ed6eee83dbddf7b0fc64dd5d4454bc05e6ccaafff47991f73f2894d9ff4/Tempita-0.5.2.tar.gz";
2181 2181 sha256 = "177wwq45slfyajd8csy477bmdmzipyw0dm7i85k3akb7m85wzkna";
2182 2182 };
2183 2183 meta = {
2184 2184 license = [ pkgs.lib.licenses.mit ];
2185 2185 };
2186 2186 };
2187 2187 "termcolor" = super.buildPythonPackage {
2188 2188 name = "termcolor-1.1.0";
2189 2189 doCheck = false;
2190 2190 src = fetchurl {
2191 2191 url = "https://files.pythonhosted.org/packages/8a/48/a76be51647d0eb9f10e2a4511bf3ffb8cc1e6b14e9e4fab46173aa79f981/termcolor-1.1.0.tar.gz";
2192 2192 sha256 = "0fv1vq14rpqwgazxg4981904lfyp84mnammw7y046491cv76jv8x";
2193 2193 };
2194 2194 meta = {
2195 2195 license = [ pkgs.lib.licenses.mit ];
2196 2196 };
2197 2197 };
2198 2198 "testpath" = super.buildPythonPackage {
2199 2199 name = "testpath-0.4.4";
2200 2200 doCheck = false;
2201 2201 src = fetchurl {
2202 2202 url = "https://files.pythonhosted.org/packages/2c/b3/5d57205e896d8998d77ad12aa42ebce75cd97d8b9a97d00ba078c4c9ffeb/testpath-0.4.4.tar.gz";
2203 2203 sha256 = "0zpcmq22dz79ipvvsfnw1ykpjcaj6xyzy7ws77s5b5ql3hka7q30";
2204 2204 };
2205 2205 meta = {
2206 2206 license = [ ];
2207 2207 };
2208 2208 };
2209 2209 "traitlets" = super.buildPythonPackage {
2210 2210 name = "traitlets-4.3.3";
2211 2211 doCheck = false;
2212 2212 propagatedBuildInputs = [
2213 2213 self."ipython-genutils"
2214 2214 self."six"
2215 2215 self."decorator"
2216 2216 self."enum34"
2217 2217 ];
2218 2218 src = fetchurl {
2219 2219 url = "https://files.pythonhosted.org/packages/75/b0/43deb021bc943f18f07cbe3dac1d681626a48997b7ffa1e7fb14ef922b21/traitlets-4.3.3.tar.gz";
2220 2220 sha256 = "1xsrwgivpkxlbr4dfndfsi098s29yqgswgjc1qqn69yxklvfw8yh";
2221 2221 };
2222 2222 meta = {
2223 2223 license = [ pkgs.lib.licenses.bsdOriginal ];
2224 2224 };
2225 2225 };
2226 2226 "transaction" = super.buildPythonPackage {
2227 2227 name = "transaction-2.4.0";
2228 2228 doCheck = false;
2229 2229 propagatedBuildInputs = [
2230 2230 self."zope.interface"
2231 2231 ];
2232 2232 src = fetchurl {
2233 2233 url = "https://files.pythonhosted.org/packages/9d/7d/0e8af0d059e052b9dcf2bb5a08aad20ae3e238746bdd3f8701a60969b363/transaction-2.4.0.tar.gz";
2234 2234 sha256 = "17wz1y524ca07vr03yddy8dv0gbscs06dbdywmllxv5rc725jq3j";
2235 2235 };
2236 2236 meta = {
2237 2237 license = [ pkgs.lib.licenses.zpl21 ];
2238 2238 };
2239 2239 };
2240 2240 "translationstring" = super.buildPythonPackage {
2241 2241 name = "translationstring-1.3";
2242 2242 doCheck = false;
2243 2243 src = fetchurl {
2244 2244 url = "https://files.pythonhosted.org/packages/5e/eb/bee578cc150b44c653b63f5ebe258b5d0d812ddac12497e5f80fcad5d0b4/translationstring-1.3.tar.gz";
2245 2245 sha256 = "0bdpcnd9pv0131dl08h4zbcwmgc45lyvq3pa224xwan5b3x4rr2f";
2246 2246 };
2247 2247 meta = {
2248 2248 license = [ { fullName = "BSD-like (http://repoze.org/license.html)"; } ];
2249 2249 };
2250 2250 };
2251 2251 "tzlocal" = super.buildPythonPackage {
2252 2252 name = "tzlocal-1.5.1";
2253 2253 doCheck = false;
2254 2254 propagatedBuildInputs = [
2255 2255 self."pytz"
2256 2256 ];
2257 2257 src = fetchurl {
2258 2258 url = "https://files.pythonhosted.org/packages/cb/89/e3687d3ed99bc882793f82634e9824e62499fdfdc4b1ae39e211c5b05017/tzlocal-1.5.1.tar.gz";
2259 2259 sha256 = "0kiciwiqx0bv0fbc913idxibc4ygg4cb7f8rcpd9ij2shi4bigjf";
2260 2260 };
2261 2261 meta = {
2262 2262 license = [ pkgs.lib.licenses.mit ];
2263 2263 };
2264 2264 };
2265 2265 "urllib3" = super.buildPythonPackage {
2266 2266 name = "urllib3-1.25.2";
2267 2267 doCheck = false;
2268 2268 src = fetchurl {
2269 2269 url = "https://files.pythonhosted.org/packages/9a/8b/ea6d2beb2da6e331e9857d0a60b79ed4f72dcbc4e2c7f2d2521b0480fda2/urllib3-1.25.2.tar.gz";
2270 2270 sha256 = "1nq2k4pss1ihsjh02r41sqpjpm5rfqkjfysyq7g7n2i1p7c66c55";
2271 2271 };
2272 2272 meta = {
2273 2273 license = [ pkgs.lib.licenses.mit ];
2274 2274 };
2275 2275 };
2276 2276 "urlobject" = super.buildPythonPackage {
2277 2277 name = "urlobject-2.4.3";
2278 2278 doCheck = false;
2279 2279 src = fetchurl {
2280 2280 url = "https://files.pythonhosted.org/packages/e2/b8/1d0a916f4b34c4618846e6da0e4eeaa8fcb4a2f39e006434fe38acb74b34/URLObject-2.4.3.tar.gz";
2281 2281 sha256 = "1ahc8ficzfvr2avln71immfh4ls0zyv6cdaa5xmkdj5rd87f5cj7";
2282 2282 };
2283 2283 meta = {
2284 2284 license = [ pkgs.lib.licenses.publicDomain ];
2285 2285 };
2286 2286 };
2287 2287 "venusian" = super.buildPythonPackage {
2288 2288 name = "venusian-1.2.0";
2289 2289 doCheck = false;
2290 2290 src = fetchurl {
2291 2291 url = "https://files.pythonhosted.org/packages/7e/6f/40a9d43ac77cb51cb62be5b5662d170f43f8037bdc4eab56336c4ca92bb7/venusian-1.2.0.tar.gz";
2292 2292 sha256 = "0ghyx66g8ikx9nx1mnwqvdcqm11i1vlq0hnvwl50s48bp22q5v34";
2293 2293 };
2294 2294 meta = {
2295 2295 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
2296 2296 };
2297 2297 };
2298 2298 "vine" = super.buildPythonPackage {
2299 2299 name = "vine-1.3.0";
2300 2300 doCheck = false;
2301 2301 src = fetchurl {
2302 2302 url = "https://files.pythonhosted.org/packages/1c/e1/79fb8046e607dd6c2ad05c9b8ebac9d0bd31d086a08f02699e96fc5b3046/vine-1.3.0.tar.gz";
2303 2303 sha256 = "11ydsbhl1vabndc2r979dv61s6j2b0giq6dgvryifvq1m7bycghk";
2304 2304 };
2305 2305 meta = {
2306 2306 license = [ pkgs.lib.licenses.bsdOriginal ];
2307 2307 };
2308 2308 };
2309 2309 "waitress" = super.buildPythonPackage {
2310 2310 name = "waitress-1.3.1";
2311 2311 doCheck = false;
2312 2312 src = fetchurl {
2313 2313 url = "https://files.pythonhosted.org/packages/a6/e6/708da7bba65898e5d759ade8391b1077e49d07be0b0223c39f5be04def56/waitress-1.3.1.tar.gz";
2314 2314 sha256 = "1iysl8ka3l4cdrr0r19fh1cv28q41mwpvgsb81ji7k4shkb0k3i7";
2315 2315 };
2316 2316 meta = {
2317 2317 license = [ pkgs.lib.licenses.zpl21 ];
2318 2318 };
2319 2319 };
2320 2320 "wcwidth" = super.buildPythonPackage {
2321 2321 name = "wcwidth-0.1.9";
2322 2322 doCheck = false;
2323 2323 src = fetchurl {
2324 2324 url = "https://files.pythonhosted.org/packages/25/9d/0acbed6e4a4be4fc99148f275488580968f44ddb5e69b8ceb53fc9df55a0/wcwidth-0.1.9.tar.gz";
2325 2325 sha256 = "1wf5ycjx8s066rdvr0fgz4xds9a8zhs91c4jzxvvymm1c8l8cwzf";
2326 2326 };
2327 2327 meta = {
2328 2328 license = [ pkgs.lib.licenses.mit ];
2329 2329 };
2330 2330 };
2331 2331 "webencodings" = super.buildPythonPackage {
2332 2332 name = "webencodings-0.5.1";
2333 2333 doCheck = false;
2334 2334 src = fetchurl {
2335 2335 url = "https://files.pythonhosted.org/packages/0b/02/ae6ceac1baeda530866a85075641cec12989bd8d31af6d5ab4a3e8c92f47/webencodings-0.5.1.tar.gz";
2336 2336 sha256 = "08qrgrc4hrximb2gqnl69g01s93rhf2842jfxdjljc1dbwj1qsmk";
2337 2337 };
2338 2338 meta = {
2339 2339 license = [ pkgs.lib.licenses.bsdOriginal ];
2340 2340 };
2341 2341 };
2342 2342 "weberror" = super.buildPythonPackage {
2343 2343 name = "weberror-0.13.1";
2344 2344 doCheck = false;
2345 2345 propagatedBuildInputs = [
2346 2346 self."webob"
2347 2347 self."tempita"
2348 2348 self."pygments"
2349 2349 self."paste"
2350 2350 ];
2351 2351 src = fetchurl {
2352 2352 url = "https://files.pythonhosted.org/packages/07/0a/09ca5eb0fab5c0d17b380026babe81c96ecebb13f2b06c3203432dd7be72/WebError-0.13.1.tar.gz";
2353 2353 sha256 = "0r4qvnf2r92gfnpa1kwygh4j2x6j3axg2i4an6hyxwg2gpaqp7y1";
2354 2354 };
2355 2355 meta = {
2356 2356 license = [ pkgs.lib.licenses.mit ];
2357 2357 };
2358 2358 };
2359 2359 "webhelpers2" = super.buildPythonPackage {
2360 2360 name = "webhelpers2-2.0";
2361 2361 doCheck = false;
2362 2362 propagatedBuildInputs = [
2363 2363 self."markupsafe"
2364 2364 self."six"
2365 2365 ];
2366 2366 src = fetchurl {
2367 2367 url = "https://files.pythonhosted.org/packages/ff/30/56342c6ea522439e3662427c8d7b5e5b390dff4ff2dc92d8afcb8ab68b75/WebHelpers2-2.0.tar.gz";
2368 2368 sha256 = "0aphva1qmxh83n01p53f5fd43m4srzbnfbz5ajvbx9aj2aipwmcs";
2369 2369 };
2370 2370 meta = {
2371 2371 license = [ pkgs.lib.licenses.mit ];
2372 2372 };
2373 2373 };
2374 2374 "webob" = super.buildPythonPackage {
2375 2375 name = "webob-1.8.5";
2376 2376 doCheck = false;
2377 2377 src = fetchurl {
2378 2378 url = "https://files.pythonhosted.org/packages/9d/1a/0c89c070ee2829c934cb6c7082287c822e28236a4fcf90063e6be7c35532/WebOb-1.8.5.tar.gz";
2379 2379 sha256 = "11khpzaxc88q31v25ic330gsf56fwmbdc9b30br8mvp0fmwspah5";
2380 2380 };
2381 2381 meta = {
2382 2382 license = [ pkgs.lib.licenses.mit ];
2383 2383 };
2384 2384 };
2385 2385 "webtest" = super.buildPythonPackage {
2386 2386 name = "webtest-2.0.34";
2387 2387 doCheck = false;
2388 2388 propagatedBuildInputs = [
2389 2389 self."six"
2390 2390 self."webob"
2391 2391 self."waitress"
2392 2392 self."beautifulsoup4"
2393 2393 ];
2394 2394 src = fetchurl {
2395 2395 url = "https://files.pythonhosted.org/packages/2c/74/a0e63feee438735d628631e2b70d82280276a930637ac535479e5fad9427/WebTest-2.0.34.tar.gz";
2396 2396 sha256 = "0x1y2c8z4fmpsny4hbp6ka37si2g10r5r2jwxhvv5mx7g3blq4bi";
2397 2397 };
2398 2398 meta = {
2399 2399 license = [ pkgs.lib.licenses.mit ];
2400 2400 };
2401 2401 };
2402 2402 "whoosh" = super.buildPythonPackage {
2403 2403 name = "whoosh-2.7.4";
2404 2404 doCheck = false;
2405 2405 src = fetchurl {
2406 2406 url = "https://files.pythonhosted.org/packages/25/2b/6beed2107b148edc1321da0d489afc4617b9ed317ef7b72d4993cad9b684/Whoosh-2.7.4.tar.gz";
2407 2407 sha256 = "10qsqdjpbc85fykc1vgcs8xwbgn4l2l52c8d83xf1q59pwyn79bw";
2408 2408 };
2409 2409 meta = {
2410 2410 license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.bsd2 ];
2411 2411 };
2412 2412 };
2413 2413 "ws4py" = super.buildPythonPackage {
2414 2414 name = "ws4py-0.5.1";
2415 2415 doCheck = false;
2416 2416 src = fetchurl {
2417 2417 url = "https://files.pythonhosted.org/packages/53/20/4019a739b2eefe9282d3822ef6a225250af964b117356971bd55e274193c/ws4py-0.5.1.tar.gz";
2418 2418 sha256 = "10slbbf2jm4hpr92jx7kh7mhf48sjl01v2w4d8z3f1p0ybbp7l19";
2419 2419 };
2420 2420 meta = {
2421 2421 license = [ pkgs.lib.licenses.bsdOriginal ];
2422 2422 };
2423 2423 };
2424 2424 "wsgiref" = super.buildPythonPackage {
2425 2425 name = "wsgiref-0.1.2";
2426 2426 doCheck = false;
2427 2427 src = fetchurl {
2428 2428 url = "https://files.pythonhosted.org/packages/41/9e/309259ce8dff8c596e8c26df86dbc4e848b9249fd36797fd60be456f03fc/wsgiref-0.1.2.zip";
2429 2429 sha256 = "0y8fyjmpq7vwwm4x732w97qbkw78rjwal5409k04cw4m03411rn7";
2430 2430 };
2431 2431 meta = {
2432 2432 license = [ { fullName = "PSF or ZPL"; } ];
2433 2433 };
2434 2434 };
2435 2435 "zipp" = super.buildPythonPackage {
2436 2436 name = "zipp-1.2.0";
2437 2437 doCheck = false;
2438 2438 propagatedBuildInputs = [
2439 2439 self."contextlib2"
2440 2440 ];
2441 2441 src = fetchurl {
2442 2442 url = "https://files.pythonhosted.org/packages/78/08/d52f0ea643bc1068d6dc98b412f4966a9b63255d20911a23ac3220c033c4/zipp-1.2.0.tar.gz";
2443 2443 sha256 = "1c91lnv1bxjimh8as27hz7bghsjkkbxn1d37xq7in9c82iai0167";
2444 2444 };
2445 2445 meta = {
2446 2446 license = [ pkgs.lib.licenses.mit ];
2447 2447 };
2448 2448 };
2449 2449 "zope.cachedescriptors" = super.buildPythonPackage {
2450 2450 name = "zope.cachedescriptors-4.3.1";
2451 2451 doCheck = false;
2452 2452 propagatedBuildInputs = [
2453 2453 self."setuptools"
2454 2454 ];
2455 2455 src = fetchurl {
2456 2456 url = "https://files.pythonhosted.org/packages/2f/89/ebe1890cc6d3291ebc935558fa764d5fffe571018dbbee200e9db78762cb/zope.cachedescriptors-4.3.1.tar.gz";
2457 2457 sha256 = "0jhr3m5p74c6r7k8iv0005b8bfsialih9d7zl5vx38rf5xq1lk8z";
2458 2458 };
2459 2459 meta = {
2460 2460 license = [ pkgs.lib.licenses.zpl21 ];
2461 2461 };
2462 2462 };
2463 2463 "zope.deprecation" = super.buildPythonPackage {
2464 2464 name = "zope.deprecation-4.4.0";
2465 2465 doCheck = false;
2466 2466 propagatedBuildInputs = [
2467 2467 self."setuptools"
2468 2468 ];
2469 2469 src = fetchurl {
2470 2470 url = "https://files.pythonhosted.org/packages/34/da/46e92d32d545dd067b9436279d84c339e8b16de2ca393d7b892bc1e1e9fd/zope.deprecation-4.4.0.tar.gz";
2471 2471 sha256 = "1pz2cv7gv9y1r3m0bdv7ks1alagmrn5msm5spwdzkb2by0w36i8d";
2472 2472 };
2473 2473 meta = {
2474 2474 license = [ pkgs.lib.licenses.zpl21 ];
2475 2475 };
2476 2476 };
2477 2477 "zope.event" = super.buildPythonPackage {
2478 2478 name = "zope.event-4.4";
2479 2479 doCheck = false;
2480 2480 propagatedBuildInputs = [
2481 2481 self."setuptools"
2482 2482 ];
2483 2483 src = fetchurl {
2484 2484 url = "https://files.pythonhosted.org/packages/4c/b2/51c0369adcf5be2334280eed230192ab3b03f81f8efda9ddea6f65cc7b32/zope.event-4.4.tar.gz";
2485 2485 sha256 = "1ksbc726av9xacml6jhcfyn828hlhb9xlddpx6fcvnlvmpmpvhk9";
2486 2486 };
2487 2487 meta = {
2488 2488 license = [ pkgs.lib.licenses.zpl21 ];
2489 2489 };
2490 2490 };
2491 2491 "zope.interface" = super.buildPythonPackage {
2492 2492 name = "zope.interface-4.6.0";
2493 2493 doCheck = false;
2494 2494 propagatedBuildInputs = [
2495 2495 self."setuptools"
2496 2496 ];
2497 2497 src = fetchurl {
2498 2498 url = "https://files.pythonhosted.org/packages/4e/d0/c9d16bd5b38de44a20c6dc5d5ed80a49626fafcb3db9f9efdc2a19026db6/zope.interface-4.6.0.tar.gz";
2499 2499 sha256 = "1rgh2x3rcl9r0v0499kf78xy86rnmanajf4ywmqb943wpk50sg8v";
2500 2500 };
2501 2501 meta = {
2502 2502 license = [ pkgs.lib.licenses.zpl21 ];
2503 2503 };
2504 2504 };
2505 2505
2506 2506 ### Test requirements
2507 2507
2508 2508
2509 2509 }
@@ -1,555 +1,564 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2020 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 itertools
22 22 import logging
23 23 import sys
24 24 import types
25 25 import fnmatch
26 26
27 27 import decorator
28 28 import venusian
29 29 from collections import OrderedDict
30 30
31 31 from pyramid.exceptions import ConfigurationError
32 32 from pyramid.renderers import render
33 33 from pyramid.response import Response
34 34 from pyramid.httpexceptions import HTTPNotFound
35 35
36 36 from rhodecode.api.exc import (
37 37 JSONRPCBaseError, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
38 38 from rhodecode.apps._base import TemplateArgs
39 39 from rhodecode.lib.auth import AuthUser
40 40 from rhodecode.lib.base import get_ip_addr, attach_context_attributes
41 41 from rhodecode.lib.exc_tracking import store_exception
42 42 from rhodecode.lib.ext_json import json
43 43 from rhodecode.lib.utils2 import safe_str
44 44 from rhodecode.lib.plugins.utils import get_plugin_settings
45 45 from rhodecode.model.db import User, UserApiKeys
46 46
47 47 log = logging.getLogger(__name__)
48 48
49 49 DEFAULT_RENDERER = 'jsonrpc_renderer'
50 50 DEFAULT_URL = '/_admin/apiv2'
51 51
52 52
53 53 def find_methods(jsonrpc_methods, pattern):
54 54 matches = OrderedDict()
55 55 if not isinstance(pattern, (list, tuple)):
56 56 pattern = [pattern]
57 57
58 58 for single_pattern in pattern:
59 59 for method_name, method in jsonrpc_methods.items():
60 60 if fnmatch.fnmatch(method_name, single_pattern):
61 61 matches[method_name] = method
62 62 return matches
63 63
64 64
65 65 class ExtJsonRenderer(object):
66 66 """
67 67 Custom renderer that mkaes use of our ext_json lib
68 68
69 69 """
70 70
71 71 def __init__(self, serializer=json.dumps, **kw):
72 72 """ Any keyword arguments will be passed to the ``serializer``
73 73 function."""
74 74 self.serializer = serializer
75 75 self.kw = kw
76 76
77 77 def __call__(self, info):
78 78 """ Returns a plain JSON-encoded string with content-type
79 79 ``application/json``. The content-type may be overridden by
80 80 setting ``request.response.content_type``."""
81 81
82 82 def _render(value, system):
83 83 request = system.get('request')
84 84 if request is not None:
85 85 response = request.response
86 86 ct = response.content_type
87 87 if ct == response.default_content_type:
88 88 response.content_type = 'application/json'
89 89
90 90 return self.serializer(value, **self.kw)
91 91
92 92 return _render
93 93
94 94
95 95 def jsonrpc_response(request, result):
96 96 rpc_id = getattr(request, 'rpc_id', None)
97 97 response = request.response
98 98
99 99 # store content_type before render is called
100 100 ct = response.content_type
101 101
102 102 ret_value = ''
103 103 if rpc_id:
104 104 ret_value = {
105 105 'id': rpc_id,
106 106 'result': result,
107 107 'error': None,
108 108 }
109 109
110 110 # fetch deprecation warnings, and store it inside results
111 111 deprecation = getattr(request, 'rpc_deprecation', None)
112 112 if deprecation:
113 113 ret_value['DEPRECATION_WARNING'] = deprecation
114 114
115 115 raw_body = render(DEFAULT_RENDERER, ret_value, request=request)
116 116 response.body = safe_str(raw_body, response.charset)
117 117
118 118 if ct == response.default_content_type:
119 119 response.content_type = 'application/json'
120 120
121 121 return response
122 122
123 123
124 124 def jsonrpc_error(request, message, retid=None, code=None, headers=None):
125 125 """
126 126 Generate a Response object with a JSON-RPC error body
127 127
128 128 :param code:
129 129 :param retid:
130 130 :param message:
131 131 """
132 132 err_dict = {'id': retid, 'result': None, 'error': message}
133 133 body = render(DEFAULT_RENDERER, err_dict, request=request).encode('utf-8')
134 134
135 135 return Response(
136 136 body=body,
137 137 status=code,
138 138 content_type='application/json',
139 139 headerlist=headers
140 140 )
141 141
142 142
143 143 def exception_view(exc, request):
144 144 rpc_id = getattr(request, 'rpc_id', None)
145 145
146 146 if isinstance(exc, JSONRPCError):
147 147 fault_message = safe_str(exc.message)
148 148 log.debug('json-rpc error rpc_id:%s "%s"', rpc_id, fault_message)
149 149 elif isinstance(exc, JSONRPCValidationError):
150 150 colander_exc = exc.colander_exception
151 151 # TODO(marcink): think maybe of nicer way to serialize errors ?
152 152 fault_message = colander_exc.asdict()
153 153 log.debug('json-rpc colander error rpc_id:%s "%s"', rpc_id, fault_message)
154 154 elif isinstance(exc, JSONRPCForbidden):
155 155 fault_message = 'Access was denied to this resource.'
156 156 log.warning('json-rpc forbidden call rpc_id:%s "%s"', rpc_id, fault_message)
157 157 elif isinstance(exc, HTTPNotFound):
158 158 method = request.rpc_method
159 159 log.debug('json-rpc method `%s` not found in list of '
160 160 'api calls: %s, rpc_id:%s',
161 161 method, request.registry.jsonrpc_methods.keys(), rpc_id)
162 162
163 163 similar = 'none'
164 164 try:
165 165 similar_paterns = ['*{}*'.format(x) for x in method.split('_')]
166 166 similar_found = find_methods(
167 167 request.registry.jsonrpc_methods, similar_paterns)
168 168 similar = ', '.join(similar_found.keys()) or similar
169 169 except Exception:
170 170 # make the whole above block safe
171 171 pass
172 172
173 173 fault_message = "No such method: {}. Similar methods: {}".format(
174 174 method, similar)
175 175 else:
176 176 fault_message = 'undefined error'
177 177 exc_info = exc.exc_info()
178 178 store_exception(id(exc_info), exc_info, prefix='rhodecode-api')
179 179
180 180 return jsonrpc_error(request, fault_message, rpc_id)
181 181
182 182
183 183 def request_view(request):
184 184 """
185 185 Main request handling method. It handles all logic to call a specific
186 186 exposed method
187 187 """
188 188 # cython compatible inspect
189 189 from rhodecode.config.patches import inspect_getargspec
190 190 inspect = inspect_getargspec()
191 191
192 192 # check if we can find this session using api_key, get_by_auth_token
193 193 # search not expired tokens only
194 194 try:
195 195 api_user = User.get_by_auth_token(request.rpc_api_key)
196 196
197 197 if api_user is None:
198 198 return jsonrpc_error(
199 199 request, retid=request.rpc_id, message='Invalid API KEY')
200 200
201 201 if not api_user.active:
202 202 return jsonrpc_error(
203 203 request, retid=request.rpc_id,
204 204 message='Request from this user not allowed')
205 205
206 206 # check if we are allowed to use this IP
207 207 auth_u = AuthUser(
208 208 api_user.user_id, request.rpc_api_key, ip_addr=request.rpc_ip_addr)
209 209 if not auth_u.ip_allowed:
210 210 return jsonrpc_error(
211 211 request, retid=request.rpc_id,
212 212 message='Request from IP:%s not allowed' % (
213 213 request.rpc_ip_addr,))
214 214 else:
215 215 log.info('Access for IP:%s allowed', request.rpc_ip_addr)
216 216
217 217 # register our auth-user
218 218 request.rpc_user = auth_u
219 219 request.environ['rc_auth_user_id'] = auth_u.user_id
220 220
221 221 # now check if token is valid for API
222 222 auth_token = request.rpc_api_key
223 223 token_match = api_user.authenticate_by_token(
224 224 auth_token, roles=[UserApiKeys.ROLE_API])
225 225 invalid_token = not token_match
226 226
227 227 log.debug('Checking if API KEY is valid with proper role')
228 228 if invalid_token:
229 229 return jsonrpc_error(
230 230 request, retid=request.rpc_id,
231 231 message='API KEY invalid or, has bad role for an API call')
232 232
233 233 except Exception:
234 234 log.exception('Error on API AUTH')
235 235 return jsonrpc_error(
236 236 request, retid=request.rpc_id, message='Invalid API KEY')
237 237
238 238 method = request.rpc_method
239 239 func = request.registry.jsonrpc_methods[method]
240 240
241 241 # now that we have a method, add request._req_params to
242 242 # self.kargs and dispatch control to WGIController
243 243 argspec = inspect.getargspec(func)
244 244 arglist = argspec[0]
245 245 defaults = map(type, argspec[3] or [])
246 246 default_empty = types.NotImplementedType
247 247
248 248 # kw arguments required by this method
249 249 func_kwargs = dict(itertools.izip_longest(
250 250 reversed(arglist), reversed(defaults), fillvalue=default_empty))
251 251
252 252 # This attribute will need to be first param of a method that uses
253 253 # api_key, which is translated to instance of user at that name
254 254 user_var = 'apiuser'
255 255 request_var = 'request'
256 256
257 257 for arg in [user_var, request_var]:
258 258 if arg not in arglist:
259 259 return jsonrpc_error(
260 260 request,
261 261 retid=request.rpc_id,
262 262 message='This method [%s] does not support '
263 263 'required parameter `%s`' % (func.__name__, arg))
264 264
265 265 # get our arglist and check if we provided them as args
266 266 for arg, default in func_kwargs.items():
267 267 if arg in [user_var, request_var]:
268 268 # user_var and request_var are pre-hardcoded parameters and we
269 269 # don't need to do any translation
270 270 continue
271 271
272 272 # skip the required param check if it's default value is
273 273 # NotImplementedType (default_empty)
274 274 if default == default_empty and arg not in request.rpc_params:
275 275 return jsonrpc_error(
276 276 request,
277 277 retid=request.rpc_id,
278 278 message=('Missing non optional `%s` arg in JSON DATA' % arg)
279 279 )
280 280
281 281 # sanitize extra passed arguments
282 282 for k in request.rpc_params.keys()[:]:
283 283 if k not in func_kwargs:
284 284 del request.rpc_params[k]
285 285
286 286 call_params = request.rpc_params
287 287 call_params.update({
288 288 'request': request,
289 289 'apiuser': auth_u
290 290 })
291 291
292 292 # register some common functions for usage
293 293 attach_context_attributes(TemplateArgs(), request, request.rpc_user.user_id)
294 294
295 295 try:
296 296 ret_value = func(**call_params)
297 297 return jsonrpc_response(request, ret_value)
298 298 except JSONRPCBaseError:
299 299 raise
300 300 except Exception:
301 301 log.exception('Unhandled exception occurred on api call: %s', func)
302 302 exc_info = sys.exc_info()
303 303 exc_id, exc_type_name = store_exception(
304 304 id(exc_info), exc_info, prefix='rhodecode-api')
305 305 error_headers = [('RhodeCode-Exception-Id', str(exc_id)),
306 306 ('RhodeCode-Exception-Type', str(exc_type_name))]
307 307 return jsonrpc_error(
308 308 request, retid=request.rpc_id, message='Internal server error',
309 309 headers=error_headers)
310 310
311 311
312 312 def setup_request(request):
313 313 """
314 314 Parse a JSON-RPC request body. It's used inside the predicates method
315 315 to validate and bootstrap requests for usage in rpc calls.
316 316
317 317 We need to raise JSONRPCError here if we want to return some errors back to
318 318 user.
319 319 """
320 320
321 321 log.debug('Executing setup request: %r', request)
322 322 request.rpc_ip_addr = get_ip_addr(request.environ)
323 323 # TODO(marcink): deprecate GET at some point
324 324 if request.method not in ['POST', 'GET']:
325 325 log.debug('unsupported request method "%s"', request.method)
326 326 raise JSONRPCError(
327 327 'unsupported request method "%s". Please use POST' % request.method)
328 328
329 329 if 'CONTENT_LENGTH' not in request.environ:
330 330 log.debug("No Content-Length")
331 331 raise JSONRPCError("Empty body, No Content-Length in request")
332 332
333 333 else:
334 334 length = request.environ['CONTENT_LENGTH']
335 335 log.debug('Content-Length: %s', length)
336 336
337 337 if length == 0:
338 338 log.debug("Content-Length is 0")
339 339 raise JSONRPCError("Content-Length is 0")
340 340
341 341 raw_body = request.body
342 342 log.debug("Loading JSON body now")
343 343 try:
344 344 json_body = json.loads(raw_body)
345 345 except ValueError as e:
346 346 # catch JSON errors Here
347 347 raise JSONRPCError("JSON parse error ERR:%s RAW:%r" % (e, raw_body))
348 348
349 349 request.rpc_id = json_body.get('id')
350 350 request.rpc_method = json_body.get('method')
351 351
352 352 # check required base parameters
353 353 try:
354 354 api_key = json_body.get('api_key')
355 355 if not api_key:
356 356 api_key = json_body.get('auth_token')
357 357
358 358 if not api_key:
359 359 raise KeyError('api_key or auth_token')
360 360
361 361 # TODO(marcink): support passing in token in request header
362 362
363 363 request.rpc_api_key = api_key
364 364 request.rpc_id = json_body['id']
365 365 request.rpc_method = json_body['method']
366 366 request.rpc_params = json_body['args'] \
367 367 if isinstance(json_body['args'], dict) else {}
368 368
369 369 log.debug('method: %s, params: %.10240r', request.rpc_method, request.rpc_params)
370 370 except KeyError as e:
371 371 raise JSONRPCError('Incorrect JSON data. Missing %s' % e)
372 372
373 373 log.debug('setup complete, now handling method:%s rpcid:%s',
374 374 request.rpc_method, request.rpc_id, )
375 375
376 376
377 377 class RoutePredicate(object):
378 378 def __init__(self, val, config):
379 379 self.val = val
380 380
381 381 def text(self):
382 382 return 'jsonrpc route = %s' % self.val
383 383
384 384 phash = text
385 385
386 386 def __call__(self, info, request):
387 387 if self.val:
388 388 # potentially setup and bootstrap our call
389 389 setup_request(request)
390 390
391 391 # Always return True so that even if it isn't a valid RPC it
392 392 # will fall through to the underlaying handlers like notfound_view
393 393 return True
394 394
395 395
396 396 class NotFoundPredicate(object):
397 397 def __init__(self, val, config):
398 398 self.val = val
399 399 self.methods = config.registry.jsonrpc_methods
400 400
401 401 def text(self):
402 402 return 'jsonrpc method not found = {}.'.format(self.val)
403 403
404 404 phash = text
405 405
406 406 def __call__(self, info, request):
407 407 return hasattr(request, 'rpc_method')
408 408
409 409
410 410 class MethodPredicate(object):
411 411 def __init__(self, val, config):
412 412 self.method = val
413 413
414 414 def text(self):
415 415 return 'jsonrpc method = %s' % self.method
416 416
417 417 phash = text
418 418
419 419 def __call__(self, context, request):
420 420 # we need to explicitly return False here, so pyramid doesn't try to
421 421 # execute our view directly. We need our main handler to execute things
422 422 return getattr(request, 'rpc_method') == self.method
423 423
424 424
425 425 def add_jsonrpc_method(config, view, **kwargs):
426 426 # pop the method name
427 427 method = kwargs.pop('method', None)
428 428
429 429 if method is None:
430 430 raise ConfigurationError(
431 431 'Cannot register a JSON-RPC method without specifying the "method"')
432 432
433 433 # we define custom predicate, to enable to detect conflicting methods,
434 434 # those predicates are kind of "translation" from the decorator variables
435 435 # to internal predicates names
436 436
437 437 kwargs['jsonrpc_method'] = method
438 438
439 439 # register our view into global view store for validation
440 440 config.registry.jsonrpc_methods[method] = view
441 441
442 442 # we're using our main request_view handler, here, so each method
443 443 # has a unified handler for itself
444 444 config.add_view(request_view, route_name='apiv2', **kwargs)
445 445
446 446
447 447 class jsonrpc_method(object):
448 448 """
449 449 decorator that works similar to @add_view_config decorator,
450 450 but tailored for our JSON RPC
451 451 """
452 452
453 453 venusian = venusian # for testing injection
454 454
455 455 def __init__(self, method=None, **kwargs):
456 456 self.method = method
457 457 self.kwargs = kwargs
458 458
459 459 def __call__(self, wrapped):
460 460 kwargs = self.kwargs.copy()
461 461 kwargs['method'] = self.method or wrapped.__name__
462 462 depth = kwargs.pop('_depth', 0)
463 463
464 464 def callback(context, name, ob):
465 465 config = context.config.with_package(info.module)
466 466 config.add_jsonrpc_method(view=ob, **kwargs)
467 467
468 468 info = venusian.attach(wrapped, callback, category='pyramid',
469 469 depth=depth + 1)
470 470 if info.scope == 'class':
471 471 # ensure that attr is set if decorating a class method
472 472 kwargs.setdefault('attr', wrapped.__name__)
473 473
474 474 kwargs['_info'] = info.codeinfo # fbo action_method
475 475 return wrapped
476 476
477 477
478 478 class jsonrpc_deprecated_method(object):
479 479 """
480 480 Marks method as deprecated, adds log.warning, and inject special key to
481 481 the request variable to mark method as deprecated.
482 482 Also injects special docstring that extract_docs will catch to mark
483 483 method as deprecated.
484 484
485 485 :param use_method: specify which method should be used instead of
486 486 the decorated one
487 487
488 488 Use like::
489 489
490 490 @jsonrpc_method()
491 491 @jsonrpc_deprecated_method(use_method='new_func', deprecated_at_version='3.0.0')
492 492 def old_func(request, apiuser, arg1, arg2):
493 493 ...
494 494 """
495 495
496 496 def __init__(self, use_method, deprecated_at_version):
497 497 self.use_method = use_method
498 498 self.deprecated_at_version = deprecated_at_version
499 499 self.deprecated_msg = ''
500 500
501 501 def __call__(self, func):
502 502 self.deprecated_msg = 'Please use method `{method}` instead.'.format(
503 503 method=self.use_method)
504 504
505 505 docstring = """\n
506 506 .. deprecated:: {version}
507 507
508 508 {deprecation_message}
509 509
510 510 {original_docstring}
511 511 """
512 512 func.__doc__ = docstring.format(
513 513 version=self.deprecated_at_version,
514 514 deprecation_message=self.deprecated_msg,
515 515 original_docstring=func.__doc__)
516 516 return decorator.decorator(self.__wrapper, func)
517 517
518 518 def __wrapper(self, func, *fargs, **fkwargs):
519 519 log.warning('DEPRECATED API CALL on function %s, please '
520 520 'use `%s` instead', func, self.use_method)
521 521 # alter function docstring to mark as deprecated, this is picked up
522 522 # via fabric file that generates API DOC.
523 523 result = func(*fargs, **fkwargs)
524 524
525 525 request = fargs[0]
526 526 request.rpc_deprecation = 'DEPRECATED METHOD ' + self.deprecated_msg
527 527 return result
528 528
529 529
530 def add_api_methods(config):
531 from rhodecode.api.views import (
532 deprecated_api, gist_api, pull_request_api, repo_api, repo_group_api,
533 server_api, search_api, testing_api, user_api, user_group_api)
534
535 config.scan('rhodecode.api.views')
536
537
530 538 def includeme(config):
531 539 plugin_module = 'rhodecode.api'
532 540 plugin_settings = get_plugin_settings(
533 541 plugin_module, config.registry.settings)
534 542
535 543 if not hasattr(config.registry, 'jsonrpc_methods'):
536 544 config.registry.jsonrpc_methods = OrderedDict()
537 545
538 546 # match filter by given method only
539 547 config.add_view_predicate('jsonrpc_method', MethodPredicate)
540 548 config.add_view_predicate('jsonrpc_method_not_found', NotFoundPredicate)
541 549
542 550 config.add_renderer(DEFAULT_RENDERER, ExtJsonRenderer(
543 551 serializer=json.dumps, indent=4))
544 552 config.add_directive('add_jsonrpc_method', add_jsonrpc_method)
545 553
546 554 config.add_route_predicate(
547 555 'jsonrpc_call', RoutePredicate)
548 556
549 557 config.add_route(
550 558 'apiv2', plugin_settings.get('url', DEFAULT_URL), jsonrpc_call=True)
551 559
552 config.scan(plugin_module, ignore='rhodecode.api.tests')
553 560 # register some exception handling view
554 561 config.add_view(exception_view, context=JSONRPCBaseError)
555 562 config.add_notfound_view(exception_view, jsonrpc_method_not_found=True)
563
564 add_api_methods(config)
@@ -1,146 +1,148 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2020 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._base.interfaces import IAdminNavigationRegistry
28 28 from rhodecode.lib.utils2 import str2bool
29 29 from rhodecode.translation import _
30 30
31 31
32 32 log = logging.getLogger(__name__)
33 33
34 34 NavListEntry = collections.namedtuple(
35 35 'NavListEntry', ['key', 'name', 'url', 'active_list'])
36 36
37 37
38 38 class NavEntry(object):
39 39 """
40 40 Represents an entry in the admin navigation.
41 41
42 42 :param key: Unique identifier used to store reference in an OrderedDict.
43 43 :param name: Display name, usually a translation string.
44 44 :param view_name: Name of the view, used generate the URL.
45 45 :param active_list: list of urls that we select active for this element
46 46 """
47 47
48 48 def __init__(self, key, name, view_name, active_list=None):
49 49 self.key = key
50 50 self.name = name
51 51 self.view_name = view_name
52 52 self._active_list = active_list or []
53 53
54 54 def generate_url(self, request):
55 55 return request.route_path(self.view_name)
56 56
57 57 def get_localized_name(self, request):
58 58 return request.translate(self.name)
59 59
60 60 @property
61 61 def active_list(self):
62 62 active_list = [self.key]
63 63 if self._active_list:
64 64 active_list = self._active_list
65 65 return active_list
66 66
67 67
68 68 @implementer(IAdminNavigationRegistry)
69 69 class NavigationRegistry(object):
70 70
71 71 _base_entries = [
72 72 NavEntry('global', _('Global'),
73 73 'admin_settings_global'),
74 74 NavEntry('vcs', _('VCS'),
75 75 'admin_settings_vcs'),
76 76 NavEntry('visual', _('Visual'),
77 77 'admin_settings_visual'),
78 78 NavEntry('mapping', _('Remap and Rescan'),
79 79 'admin_settings_mapping'),
80 80 NavEntry('issuetracker', _('Issue Tracker'),
81 81 'admin_settings_issuetracker'),
82 82 NavEntry('email', _('Email'),
83 83 'admin_settings_email'),
84 84 NavEntry('hooks', _('Hooks'),
85 85 'admin_settings_hooks'),
86 86 NavEntry('search', _('Full Text Search'),
87 87 'admin_settings_search'),
88 88 NavEntry('system', _('System Info'),
89 89 'admin_settings_system'),
90 90 NavEntry('exceptions', _('Exceptions Tracker'),
91 91 'admin_settings_exception_tracker',
92 92 active_list=['exceptions', 'exceptions_browse']),
93 93 NavEntry('process_management', _('Processes'),
94 94 'admin_settings_process_management'),
95 95 NavEntry('sessions', _('User Sessions'),
96 96 'admin_settings_sessions'),
97 97 NavEntry('open_source', _('Open Source Licenses'),
98 98 'admin_settings_open_source'),
99 99 NavEntry('automation', _('Automation'),
100 100 'admin_settings_automation')
101 101 ]
102 102
103 103 _labs_entry = NavEntry('labs', _('Labs'),
104 104 'admin_settings_labs')
105 105
106 106 def __init__(self, labs_active=False):
107 107 self._registered_entries = collections.OrderedDict()
108 108 for item in self.__class__._base_entries:
109 109 self._registered_entries[item.key] = item
110 110
111 111 if labs_active:
112 112 self.add_entry(self._labs_entry)
113 113
114 114 def add_entry(self, entry):
115 115 self._registered_entries[entry.key] = entry
116 116
117 117 def get_navlist(self, request):
118 118 nav_list = [
119 119 NavListEntry(i.key, i.get_localized_name(request),
120 120 i.generate_url(request), i.active_list)
121 121 for i in self._registered_entries.values()]
122 122 return nav_list
123 123
124 124
125 125 def navigation_registry(request, registry=None):
126 126 """
127 127 Helper that returns the admin navigation registry.
128 128 """
129 129 pyramid_registry = registry or request.registry
130 130 nav_registry = pyramid_registry.queryUtility(IAdminNavigationRegistry)
131 131 return nav_registry
132 132
133 133
134 134 def navigation_list(request):
135 135 """
136 136 Helper that returns the admin navigation as list of NavListEntry objects.
137 137 """
138 138 return navigation_registry(request).get_navlist(request)
139 139
140 140
141 141 def includeme(config):
142 142 # Create admin navigation registry and add it to the pyramid registry.
143 143 settings = config.get_settings()
144 144 labs_active = str2bool(settings.get('labs_settings_active', False))
145 145 navigation_registry_instance = NavigationRegistry(labs_active=labs_active)
146 146 config.registry.registerUtility(navigation_registry_instance)
147 log.debug('Created new nabigation instance, %s', navigation_registry_instance)
148
This diff has been collapsed as it changes many lines, (619 lines changed) Show them Hide them
@@ -1,466 +1,1055 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2020 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 from rhodecode.apps._base import ADMIN_PREFIX
23 23
24 24
25 25 def admin_routes(config):
26 26 """
27 27 Admin prefixed routes
28 28 """
29 from rhodecode.apps.admin.views.audit_logs import AdminAuditLogsView
30 from rhodecode.apps.admin.views.defaults import AdminDefaultSettingsView
31 from rhodecode.apps.admin.views.exception_tracker import ExceptionsTrackerView
32 from rhodecode.apps.admin.views.main_views import AdminMainView
33 from rhodecode.apps.admin.views.open_source_licenses import OpenSourceLicensesAdminSettingsView
34 from rhodecode.apps.admin.views.permissions import AdminPermissionsView
35 from rhodecode.apps.admin.views.process_management import AdminProcessManagementView
36 from rhodecode.apps.admin.views.repo_groups import AdminRepoGroupsView
37 from rhodecode.apps.admin.views.repositories import AdminReposView
38 from rhodecode.apps.admin.views.sessions import AdminSessionSettingsView
39 from rhodecode.apps.admin.views.settings import AdminSettingsView
40 from rhodecode.apps.admin.views.svn_config import AdminSvnConfigView
41 from rhodecode.apps.admin.views.system_info import AdminSystemInfoSettingsView
42 from rhodecode.apps.admin.views.user_groups import AdminUserGroupsView
43 from rhodecode.apps.admin.views.users import AdminUsersView, UsersView
44
29 45 config.add_route(
30 46 name='admin_audit_logs',
31 47 pattern='/audit_logs')
48 config.add_view(
49 AdminAuditLogsView,
50 attr='admin_audit_logs',
51 route_name='admin_audit_logs', request_method='GET',
52 renderer='rhodecode:templates/admin/admin_audit_logs.mako')
32 53
33 54 config.add_route(
34 55 name='admin_audit_log_entry',
35 56 pattern='/audit_logs/{audit_log_id}')
36
37 config.add_route(
38 name='pull_requests_global_0', # backward compat
39 pattern='/pull_requests/{pull_request_id:\d+}')
40 config.add_route(
41 name='pull_requests_global_1', # backward compat
42 pattern='/pull-requests/{pull_request_id:\d+}')
43 config.add_route(
44 name='pull_requests_global',
45 pattern='/pull-request/{pull_request_id:\d+}')
57 config.add_view(
58 AdminAuditLogsView,
59 attr='admin_audit_log_entry',
60 route_name='admin_audit_log_entry', request_method='GET',
61 renderer='rhodecode:templates/admin/admin_audit_log_entry.mako')
46 62
47 63 config.add_route(
48 64 name='admin_settings_open_source',
49 65 pattern='/settings/open_source')
66 config.add_view(
67 OpenSourceLicensesAdminSettingsView,
68 attr='open_source_licenses',
69 route_name='admin_settings_open_source', request_method='GET',
70 renderer='rhodecode:templates/admin/settings/settings.mako')
71
50 72 config.add_route(
51 73 name='admin_settings_vcs_svn_generate_cfg',
52 74 pattern='/settings/vcs/svn_generate_cfg')
75 config.add_view(
76 AdminSvnConfigView,
77 attr='vcs_svn_generate_config',
78 route_name='admin_settings_vcs_svn_generate_cfg',
79 request_method='POST', renderer='json')
53 80
54 81 config.add_route(
55 82 name='admin_settings_system',
56 83 pattern='/settings/system')
84 config.add_view(
85 AdminSystemInfoSettingsView,
86 attr='settings_system_info',
87 route_name='admin_settings_system', request_method='GET',
88 renderer='rhodecode:templates/admin/settings/settings.mako')
89
57 90 config.add_route(
58 91 name='admin_settings_system_update',
59 92 pattern='/settings/system/updates')
93 config.add_view(
94 AdminSystemInfoSettingsView,
95 attr='settings_system_info_check_update',
96 route_name='admin_settings_system_update', request_method='GET',
97 renderer='rhodecode:templates/admin/settings/settings_system_update.mako')
60 98
61 99 config.add_route(
62 100 name='admin_settings_exception_tracker',
63 101 pattern='/settings/exceptions')
102 config.add_view(
103 ExceptionsTrackerView,
104 attr='browse_exceptions',
105 route_name='admin_settings_exception_tracker', request_method='GET',
106 renderer='rhodecode:templates/admin/settings/settings.mako')
107
64 108 config.add_route(
65 109 name='admin_settings_exception_tracker_delete_all',
66 pattern='/settings/exceptions/delete')
110 pattern='/settings/exceptions_delete_all')
111 config.add_view(
112 ExceptionsTrackerView,
113 attr='exception_delete_all',
114 route_name='admin_settings_exception_tracker_delete_all', request_method='POST',
115 renderer='rhodecode:templates/admin/settings/settings.mako')
116
67 117 config.add_route(
68 118 name='admin_settings_exception_tracker_show',
69 119 pattern='/settings/exceptions/{exception_id}')
120 config.add_view(
121 ExceptionsTrackerView,
122 attr='exception_show',
123 route_name='admin_settings_exception_tracker_show', request_method='GET',
124 renderer='rhodecode:templates/admin/settings/settings.mako')
125
70 126 config.add_route(
71 127 name='admin_settings_exception_tracker_delete',
72 128 pattern='/settings/exceptions/{exception_id}/delete')
129 config.add_view(
130 ExceptionsTrackerView,
131 attr='exception_delete',
132 route_name='admin_settings_exception_tracker_delete', request_method='POST',
133 renderer='rhodecode:templates/admin/settings/settings.mako')
73 134
74 135 config.add_route(
75 136 name='admin_settings_sessions',
76 137 pattern='/settings/sessions')
138 config.add_view(
139 AdminSessionSettingsView,
140 attr='settings_sessions',
141 route_name='admin_settings_sessions', request_method='GET',
142 renderer='rhodecode:templates/admin/settings/settings.mako')
143
77 144 config.add_route(
78 145 name='admin_settings_sessions_cleanup',
79 146 pattern='/settings/sessions/cleanup')
147 config.add_view(
148 AdminSessionSettingsView,
149 attr='settings_sessions_cleanup',
150 route_name='admin_settings_sessions_cleanup', request_method='POST')
80 151
81 152 config.add_route(
82 153 name='admin_settings_process_management',
83 154 pattern='/settings/process_management')
155 config.add_view(
156 AdminProcessManagementView,
157 attr='process_management',
158 route_name='admin_settings_process_management', request_method='GET',
159 renderer='rhodecode:templates/admin/settings/settings.mako')
160
84 161 config.add_route(
85 162 name='admin_settings_process_management_data',
86 163 pattern='/settings/process_management/data')
164 config.add_view(
165 AdminProcessManagementView,
166 attr='process_management_data',
167 route_name='admin_settings_process_management_data', request_method='GET',
168 renderer='rhodecode:templates/admin/settings/settings_process_management_data.mako')
169
87 170 config.add_route(
88 171 name='admin_settings_process_management_signal',
89 172 pattern='/settings/process_management/signal')
173 config.add_view(
174 AdminProcessManagementView,
175 attr='process_management_signal',
176 route_name='admin_settings_process_management_signal',
177 request_method='POST', renderer='json_ext')
178
90 179 config.add_route(
91 180 name='admin_settings_process_management_master_signal',
92 181 pattern='/settings/process_management/master_signal')
182 config.add_view(
183 AdminProcessManagementView,
184 attr='process_management_master_signal',
185 route_name='admin_settings_process_management_master_signal',
186 request_method='POST', renderer='json_ext')
93 187
94 188 # default settings
95 189 config.add_route(
96 190 name='admin_defaults_repositories',
97 191 pattern='/defaults/repositories')
192 config.add_view(
193 AdminDefaultSettingsView,
194 attr='defaults_repository_show',
195 route_name='admin_defaults_repositories', request_method='GET',
196 renderer='rhodecode:templates/admin/defaults/defaults.mako')
197
98 198 config.add_route(
99 199 name='admin_defaults_repositories_update',
100 200 pattern='/defaults/repositories/update')
201 config.add_view(
202 AdminDefaultSettingsView,
203 attr='defaults_repository_update',
204 route_name='admin_defaults_repositories_update', request_method='POST',
205 renderer='rhodecode:templates/admin/defaults/defaults.mako')
101 206
102 207 # admin settings
103 208
104 209 config.add_route(
105 210 name='admin_settings',
106 211 pattern='/settings')
212 config.add_view(
213 AdminSettingsView,
214 attr='settings_global',
215 route_name='admin_settings', request_method='GET',
216 renderer='rhodecode:templates/admin/settings/settings.mako')
217
107 218 config.add_route(
108 219 name='admin_settings_update',
109 220 pattern='/settings/update')
221 config.add_view(
222 AdminSettingsView,
223 attr='settings_global_update',
224 route_name='admin_settings_update', request_method='POST',
225 renderer='rhodecode:templates/admin/settings/settings.mako')
110 226
111 227 config.add_route(
112 228 name='admin_settings_global',
113 229 pattern='/settings/global')
230 config.add_view(
231 AdminSettingsView,
232 attr='settings_global',
233 route_name='admin_settings_global', request_method='GET',
234 renderer='rhodecode:templates/admin/settings/settings.mako')
235
114 236 config.add_route(
115 237 name='admin_settings_global_update',
116 238 pattern='/settings/global/update')
239 config.add_view(
240 AdminSettingsView,
241 attr='settings_global_update',
242 route_name='admin_settings_global_update', request_method='POST',
243 renderer='rhodecode:templates/admin/settings/settings.mako')
117 244
118 245 config.add_route(
119 246 name='admin_settings_vcs',
120 247 pattern='/settings/vcs')
248 config.add_view(
249 AdminSettingsView,
250 attr='settings_vcs',
251 route_name='admin_settings_vcs', request_method='GET',
252 renderer='rhodecode:templates/admin/settings/settings.mako')
253
121 254 config.add_route(
122 255 name='admin_settings_vcs_update',
123 256 pattern='/settings/vcs/update')
257 config.add_view(
258 AdminSettingsView,
259 attr='settings_vcs_update',
260 route_name='admin_settings_vcs_update', request_method='POST',
261 renderer='rhodecode:templates/admin/settings/settings.mako')
262
124 263 config.add_route(
125 264 name='admin_settings_vcs_svn_pattern_delete',
126 265 pattern='/settings/vcs/svn_pattern_delete')
266 config.add_view(
267 AdminSettingsView,
268 attr='settings_vcs_delete_svn_pattern',
269 route_name='admin_settings_vcs_svn_pattern_delete', request_method='POST',
270 renderer='json_ext', xhr=True)
127 271
128 272 config.add_route(
129 273 name='admin_settings_mapping',
130 274 pattern='/settings/mapping')
275 config.add_view(
276 AdminSettingsView,
277 attr='settings_mapping',
278 route_name='admin_settings_mapping', request_method='GET',
279 renderer='rhodecode:templates/admin/settings/settings.mako')
280
131 281 config.add_route(
132 282 name='admin_settings_mapping_update',
133 283 pattern='/settings/mapping/update')
284 config.add_view(
285 AdminSettingsView,
286 attr='settings_mapping_update',
287 route_name='admin_settings_mapping_update', request_method='POST',
288 renderer='rhodecode:templates/admin/settings/settings.mako')
134 289
135 290 config.add_route(
136 291 name='admin_settings_visual',
137 292 pattern='/settings/visual')
293 config.add_view(
294 AdminSettingsView,
295 attr='settings_visual',
296 route_name='admin_settings_visual', request_method='GET',
297 renderer='rhodecode:templates/admin/settings/settings.mako')
298
138 299 config.add_route(
139 300 name='admin_settings_visual_update',
140 301 pattern='/settings/visual/update')
302 config.add_view(
303 AdminSettingsView,
304 attr='settings_visual_update',
305 route_name='admin_settings_visual_update', request_method='POST',
306 renderer='rhodecode:templates/admin/settings/settings.mako')
141 307
142 308 config.add_route(
143 309 name='admin_settings_issuetracker',
144 310 pattern='/settings/issue-tracker')
311 config.add_view(
312 AdminSettingsView,
313 attr='settings_issuetracker',
314 route_name='admin_settings_issuetracker', request_method='GET',
315 renderer='rhodecode:templates/admin/settings/settings.mako')
316
145 317 config.add_route(
146 318 name='admin_settings_issuetracker_update',
147 319 pattern='/settings/issue-tracker/update')
320 config.add_view(
321 AdminSettingsView,
322 attr='settings_issuetracker_update',
323 route_name='admin_settings_issuetracker_update', request_method='POST',
324 renderer='rhodecode:templates/admin/settings/settings.mako')
325
148 326 config.add_route(
149 327 name='admin_settings_issuetracker_test',
150 328 pattern='/settings/issue-tracker/test')
329 config.add_view(
330 AdminSettingsView,
331 attr='settings_issuetracker_test',
332 route_name='admin_settings_issuetracker_test', request_method='POST',
333 renderer='string', xhr=True)
334
151 335 config.add_route(
152 336 name='admin_settings_issuetracker_delete',
153 337 pattern='/settings/issue-tracker/delete')
338 config.add_view(
339 AdminSettingsView,
340 attr='settings_issuetracker_delete',
341 route_name='admin_settings_issuetracker_delete', request_method='POST',
342 renderer='json_ext', xhr=True)
154 343
155 344 config.add_route(
156 345 name='admin_settings_email',
157 346 pattern='/settings/email')
347 config.add_view(
348 AdminSettingsView,
349 attr='settings_email',
350 route_name='admin_settings_email', request_method='GET',
351 renderer='rhodecode:templates/admin/settings/settings.mako')
352
158 353 config.add_route(
159 354 name='admin_settings_email_update',
160 355 pattern='/settings/email/update')
356 config.add_view(
357 AdminSettingsView,
358 attr='settings_email_update',
359 route_name='admin_settings_email_update', request_method='POST',
360 renderer='rhodecode:templates/admin/settings/settings.mako')
161 361
162 362 config.add_route(
163 363 name='admin_settings_hooks',
164 364 pattern='/settings/hooks')
365 config.add_view(
366 AdminSettingsView,
367 attr='settings_hooks',
368 route_name='admin_settings_hooks', request_method='GET',
369 renderer='rhodecode:templates/admin/settings/settings.mako')
370
165 371 config.add_route(
166 372 name='admin_settings_hooks_update',
167 373 pattern='/settings/hooks/update')
374 config.add_view(
375 AdminSettingsView,
376 attr='settings_hooks_update',
377 route_name='admin_settings_hooks_update', request_method='POST',
378 renderer='rhodecode:templates/admin/settings/settings.mako')
379
168 380 config.add_route(
169 381 name='admin_settings_hooks_delete',
170 382 pattern='/settings/hooks/delete')
383 config.add_view(
384 AdminSettingsView,
385 attr='settings_hooks_update',
386 route_name='admin_settings_hooks_delete', request_method='POST',
387 renderer='rhodecode:templates/admin/settings/settings.mako')
171 388
172 389 config.add_route(
173 390 name='admin_settings_search',
174 391 pattern='/settings/search')
392 config.add_view(
393 AdminSettingsView,
394 attr='settings_search',
395 route_name='admin_settings_search', request_method='GET',
396 renderer='rhodecode:templates/admin/settings/settings.mako')
175 397
176 398 config.add_route(
177 399 name='admin_settings_labs',
178 400 pattern='/settings/labs')
401 config.add_view(
402 AdminSettingsView,
403 attr='settings_labs',
404 route_name='admin_settings_labs', request_method='GET',
405 renderer='rhodecode:templates/admin/settings/settings.mako')
406
179 407 config.add_route(
180 408 name='admin_settings_labs_update',
181 409 pattern='/settings/labs/update')
410 config.add_view(
411 AdminSettingsView,
412 attr='settings_labs_update',
413 route_name='admin_settings_labs_update', request_method='POST',
414 renderer='rhodecode:templates/admin/settings/settings.mako')
182 415
183 416 # Automation EE feature
184 417 config.add_route(
185 418 'admin_settings_automation',
186 419 pattern=ADMIN_PREFIX + '/settings/automation')
420 config.add_view(
421 AdminSettingsView,
422 attr='settings_automation',
423 route_name='admin_settings_automation', request_method='GET',
424 renderer='rhodecode:templates/admin/settings/settings.mako')
187 425
188 426 # global permissions
189 427
190 428 config.add_route(
191 429 name='admin_permissions_application',
192 430 pattern='/permissions/application')
431 config.add_view(
432 AdminPermissionsView,
433 attr='permissions_application',
434 route_name='admin_permissions_application', request_method='GET',
435 renderer='rhodecode:templates/admin/permissions/permissions.mako')
436
193 437 config.add_route(
194 438 name='admin_permissions_application_update',
195 439 pattern='/permissions/application/update')
440 config.add_view(
441 AdminPermissionsView,
442 attr='permissions_application_update',
443 route_name='admin_permissions_application_update', request_method='POST',
444 renderer='rhodecode:templates/admin/permissions/permissions.mako')
196 445
197 446 config.add_route(
198 447 name='admin_permissions_global',
199 448 pattern='/permissions/global')
449 config.add_view(
450 AdminPermissionsView,
451 attr='permissions_global',
452 route_name='admin_permissions_global', request_method='GET',
453 renderer='rhodecode:templates/admin/permissions/permissions.mako')
454
200 455 config.add_route(
201 456 name='admin_permissions_global_update',
202 457 pattern='/permissions/global/update')
458 config.add_view(
459 AdminPermissionsView,
460 attr='permissions_global_update',
461 route_name='admin_permissions_global_update', request_method='POST',
462 renderer='rhodecode:templates/admin/permissions/permissions.mako')
203 463
204 464 config.add_route(
205 465 name='admin_permissions_object',
206 466 pattern='/permissions/object')
467 config.add_view(
468 AdminPermissionsView,
469 attr='permissions_objects',
470 route_name='admin_permissions_object', request_method='GET',
471 renderer='rhodecode:templates/admin/permissions/permissions.mako')
472
207 473 config.add_route(
208 474 name='admin_permissions_object_update',
209 475 pattern='/permissions/object/update')
476 config.add_view(
477 AdminPermissionsView,
478 attr='permissions_objects_update',
479 route_name='admin_permissions_object_update', request_method='POST',
480 renderer='rhodecode:templates/admin/permissions/permissions.mako')
210 481
211 482 # Branch perms EE feature
212 483 config.add_route(
213 484 name='admin_permissions_branch',
214 485 pattern='/permissions/branch')
486 config.add_view(
487 AdminPermissionsView,
488 attr='permissions_branch',
489 route_name='admin_permissions_branch', request_method='GET',
490 renderer='rhodecode:templates/admin/permissions/permissions.mako')
215 491
216 492 config.add_route(
217 493 name='admin_permissions_ips',
218 494 pattern='/permissions/ips')
495 config.add_view(
496 AdminPermissionsView,
497 attr='permissions_ips',
498 route_name='admin_permissions_ips', request_method='GET',
499 renderer='rhodecode:templates/admin/permissions/permissions.mako')
219 500
220 501 config.add_route(
221 502 name='admin_permissions_overview',
222 503 pattern='/permissions/overview')
504 config.add_view(
505 AdminPermissionsView,
506 attr='permissions_overview',
507 route_name='admin_permissions_overview', request_method='GET',
508 renderer='rhodecode:templates/admin/permissions/permissions.mako')
223 509
224 510 config.add_route(
225 511 name='admin_permissions_auth_token_access',
226 512 pattern='/permissions/auth_token_access')
513 config.add_view(
514 AdminPermissionsView,
515 attr='auth_token_access',
516 route_name='admin_permissions_auth_token_access', request_method='GET',
517 renderer='rhodecode:templates/admin/permissions/permissions.mako')
227 518
228 519 config.add_route(
229 520 name='admin_permissions_ssh_keys',
230 521 pattern='/permissions/ssh_keys')
522 config.add_view(
523 AdminPermissionsView,
524 attr='ssh_keys',
525 route_name='admin_permissions_ssh_keys', request_method='GET',
526 renderer='rhodecode:templates/admin/permissions/permissions.mako')
527
231 528 config.add_route(
232 529 name='admin_permissions_ssh_keys_data',
233 530 pattern='/permissions/ssh_keys/data')
531 config.add_view(
532 AdminPermissionsView,
533 attr='ssh_keys_data',
534 route_name='admin_permissions_ssh_keys_data', request_method='GET',
535 renderer='json_ext', xhr=True)
536
234 537 config.add_route(
235 538 name='admin_permissions_ssh_keys_update',
236 539 pattern='/permissions/ssh_keys/update')
540 config.add_view(
541 AdminPermissionsView,
542 attr='ssh_keys_update',
543 route_name='admin_permissions_ssh_keys_update', request_method='POST',
544 renderer='rhodecode:templates/admin/permissions/permissions.mako')
237 545
238 546 # users admin
239 547 config.add_route(
240 548 name='users',
241 549 pattern='/users')
550 config.add_view(
551 AdminUsersView,
552 attr='users_list',
553 route_name='users', request_method='GET',
554 renderer='rhodecode:templates/admin/users/users.mako')
242 555
243 556 config.add_route(
244 557 name='users_data',
245 558 pattern='/users_data')
559 config.add_view(
560 AdminUsersView,
561 attr='users_list_data',
562 # renderer defined below
563 route_name='users_data', request_method='GET',
564 renderer='json_ext', xhr=True)
246 565
247 566 config.add_route(
248 567 name='users_create',
249 568 pattern='/users/create')
569 config.add_view(
570 AdminUsersView,
571 attr='users_create',
572 route_name='users_create', request_method='POST',
573 renderer='rhodecode:templates/admin/users/user_add.mako')
250 574
251 575 config.add_route(
252 576 name='users_new',
253 577 pattern='/users/new')
578 config.add_view(
579 AdminUsersView,
580 attr='users_new',
581 route_name='users_new', request_method='GET',
582 renderer='rhodecode:templates/admin/users/user_add.mako')
254 583
255 584 # user management
256 585 config.add_route(
257 586 name='user_edit',
258 587 pattern='/users/{user_id:\d+}/edit',
259 588 user_route=True)
589 config.add_view(
590 UsersView,
591 attr='user_edit',
592 route_name='user_edit', request_method='GET',
593 renderer='rhodecode:templates/admin/users/user_edit.mako')
594
260 595 config.add_route(
261 596 name='user_edit_advanced',
262 597 pattern='/users/{user_id:\d+}/edit/advanced',
263 598 user_route=True)
599 config.add_view(
600 UsersView,
601 attr='user_edit_advanced',
602 route_name='user_edit_advanced', request_method='GET',
603 renderer='rhodecode:templates/admin/users/user_edit.mako')
604
264 605 config.add_route(
265 606 name='user_edit_global_perms',
266 607 pattern='/users/{user_id:\d+}/edit/global_permissions',
267 608 user_route=True)
609 config.add_view(
610 UsersView,
611 attr='user_edit_global_perms',
612 route_name='user_edit_global_perms', request_method='GET',
613 renderer='rhodecode:templates/admin/users/user_edit.mako')
614
268 615 config.add_route(
269 616 name='user_edit_global_perms_update',
270 617 pattern='/users/{user_id:\d+}/edit/global_permissions/update',
271 618 user_route=True)
619 config.add_view(
620 UsersView,
621 attr='user_edit_global_perms_update',
622 route_name='user_edit_global_perms_update', request_method='POST',
623 renderer='rhodecode:templates/admin/users/user_edit.mako')
624
272 625 config.add_route(
273 626 name='user_update',
274 627 pattern='/users/{user_id:\d+}/update',
275 628 user_route=True)
629 config.add_view(
630 UsersView,
631 attr='user_update',
632 route_name='user_update', request_method='POST',
633 renderer='rhodecode:templates/admin/users/user_edit.mako')
634
276 635 config.add_route(
277 636 name='user_delete',
278 637 pattern='/users/{user_id:\d+}/delete',
279 638 user_route=True)
639 config.add_view(
640 UsersView,
641 attr='user_delete',
642 route_name='user_delete', request_method='POST',
643 renderer='rhodecode:templates/admin/users/user_edit.mako')
644
280 645 config.add_route(
281 646 name='user_enable_force_password_reset',
282 647 pattern='/users/{user_id:\d+}/password_reset_enable',
283 648 user_route=True)
649 config.add_view(
650 UsersView,
651 attr='user_enable_force_password_reset',
652 route_name='user_enable_force_password_reset', request_method='POST',
653 renderer='rhodecode:templates/admin/users/user_edit.mako')
654
284 655 config.add_route(
285 656 name='user_disable_force_password_reset',
286 657 pattern='/users/{user_id:\d+}/password_reset_disable',
287 658 user_route=True)
659 config.add_view(
660 UsersView,
661 attr='user_disable_force_password_reset',
662 route_name='user_disable_force_password_reset', request_method='POST',
663 renderer='rhodecode:templates/admin/users/user_edit.mako')
664
288 665 config.add_route(
289 666 name='user_create_personal_repo_group',
290 667 pattern='/users/{user_id:\d+}/create_repo_group',
291 668 user_route=True)
669 config.add_view(
670 UsersView,
671 attr='user_create_personal_repo_group',
672 route_name='user_create_personal_repo_group', request_method='POST',
673 renderer='rhodecode:templates/admin/users/user_edit.mako')
292 674
293 675 # user notice
294 676 config.add_route(
295 677 name='user_notice_dismiss',
296 678 pattern='/users/{user_id:\d+}/notice_dismiss',
297 679 user_route=True)
680 config.add_view(
681 UsersView,
682 attr='user_notice_dismiss',
683 route_name='user_notice_dismiss', request_method='POST',
684 renderer='json_ext', xhr=True)
298 685
299 686 # user auth tokens
300 687 config.add_route(
301 688 name='edit_user_auth_tokens',
302 689 pattern='/users/{user_id:\d+}/edit/auth_tokens',
303 690 user_route=True)
691 config.add_view(
692 UsersView,
693 attr='auth_tokens',
694 route_name='edit_user_auth_tokens', request_method='GET',
695 renderer='rhodecode:templates/admin/users/user_edit.mako')
696
304 697 config.add_route(
305 698 name='edit_user_auth_tokens_view',
306 699 pattern='/users/{user_id:\d+}/edit/auth_tokens/view',
307 700 user_route=True)
701 config.add_view(
702 UsersView,
703 attr='auth_tokens_view',
704 route_name='edit_user_auth_tokens_view', request_method='POST',
705 renderer='json_ext', xhr=True)
706
308 707 config.add_route(
309 708 name='edit_user_auth_tokens_add',
310 709 pattern='/users/{user_id:\d+}/edit/auth_tokens/new',
311 710 user_route=True)
711 config.add_view(
712 UsersView,
713 attr='auth_tokens_add',
714 route_name='edit_user_auth_tokens_add', request_method='POST')
715
312 716 config.add_route(
313 717 name='edit_user_auth_tokens_delete',
314 718 pattern='/users/{user_id:\d+}/edit/auth_tokens/delete',
315 719 user_route=True)
720 config.add_view(
721 UsersView,
722 attr='auth_tokens_delete',
723 route_name='edit_user_auth_tokens_delete', request_method='POST')
316 724
317 725 # user ssh keys
318 726 config.add_route(
319 727 name='edit_user_ssh_keys',
320 728 pattern='/users/{user_id:\d+}/edit/ssh_keys',
321 729 user_route=True)
730 config.add_view(
731 UsersView,
732 attr='ssh_keys',
733 route_name='edit_user_ssh_keys', request_method='GET',
734 renderer='rhodecode:templates/admin/users/user_edit.mako')
735
322 736 config.add_route(
323 737 name='edit_user_ssh_keys_generate_keypair',
324 738 pattern='/users/{user_id:\d+}/edit/ssh_keys/generate',
325 739 user_route=True)
740 config.add_view(
741 UsersView,
742 attr='ssh_keys_generate_keypair',
743 route_name='edit_user_ssh_keys_generate_keypair', request_method='GET',
744 renderer='rhodecode:templates/admin/users/user_edit.mako')
745
326 746 config.add_route(
327 747 name='edit_user_ssh_keys_add',
328 748 pattern='/users/{user_id:\d+}/edit/ssh_keys/new',
329 749 user_route=True)
750 config.add_view(
751 UsersView,
752 attr='ssh_keys_add',
753 route_name='edit_user_ssh_keys_add', request_method='POST')
754
330 755 config.add_route(
331 756 name='edit_user_ssh_keys_delete',
332 757 pattern='/users/{user_id:\d+}/edit/ssh_keys/delete',
333 758 user_route=True)
759 config.add_view(
760 UsersView,
761 attr='ssh_keys_delete',
762 route_name='edit_user_ssh_keys_delete', request_method='POST')
334 763
335 764 # user emails
336 765 config.add_route(
337 766 name='edit_user_emails',
338 767 pattern='/users/{user_id:\d+}/edit/emails',
339 768 user_route=True)
769 config.add_view(
770 UsersView,
771 attr='emails',
772 route_name='edit_user_emails', request_method='GET',
773 renderer='rhodecode:templates/admin/users/user_edit.mako')
774
340 775 config.add_route(
341 776 name='edit_user_emails_add',
342 777 pattern='/users/{user_id:\d+}/edit/emails/new',
343 778 user_route=True)
779 config.add_view(
780 UsersView,
781 attr='emails_add',
782 route_name='edit_user_emails_add', request_method='POST')
783
344 784 config.add_route(
345 785 name='edit_user_emails_delete',
346 786 pattern='/users/{user_id:\d+}/edit/emails/delete',
347 787 user_route=True)
788 config.add_view(
789 UsersView,
790 attr='emails_delete',
791 route_name='edit_user_emails_delete', request_method='POST')
348 792
349 793 # user IPs
350 794 config.add_route(
351 795 name='edit_user_ips',
352 796 pattern='/users/{user_id:\d+}/edit/ips',
353 797 user_route=True)
798 config.add_view(
799 UsersView,
800 attr='ips',
801 route_name='edit_user_ips', request_method='GET',
802 renderer='rhodecode:templates/admin/users/user_edit.mako')
803
354 804 config.add_route(
355 805 name='edit_user_ips_add',
356 806 pattern='/users/{user_id:\d+}/edit/ips/new',
357 807 user_route_with_default=True) # enabled for default user too
808 config.add_view(
809 UsersView,
810 attr='ips_add',
811 route_name='edit_user_ips_add', request_method='POST')
812
358 813 config.add_route(
359 814 name='edit_user_ips_delete',
360 815 pattern='/users/{user_id:\d+}/edit/ips/delete',
361 816 user_route_with_default=True) # enabled for default user too
817 config.add_view(
818 UsersView,
819 attr='ips_delete',
820 route_name='edit_user_ips_delete', request_method='POST')
362 821
363 822 # user perms
364 823 config.add_route(
365 824 name='edit_user_perms_summary',
366 825 pattern='/users/{user_id:\d+}/edit/permissions_summary',
367 826 user_route=True)
827 config.add_view(
828 UsersView,
829 attr='user_perms_summary',
830 route_name='edit_user_perms_summary', request_method='GET',
831 renderer='rhodecode:templates/admin/users/user_edit.mako')
832
368 833 config.add_route(
369 834 name='edit_user_perms_summary_json',
370 835 pattern='/users/{user_id:\d+}/edit/permissions_summary/json',
371 836 user_route=True)
837 config.add_view(
838 UsersView,
839 attr='user_perms_summary_json',
840 route_name='edit_user_perms_summary_json', request_method='GET',
841 renderer='json_ext')
372 842
373 843 # user user groups management
374 844 config.add_route(
375 845 name='edit_user_groups_management',
376 846 pattern='/users/{user_id:\d+}/edit/groups_management',
377 847 user_route=True)
848 config.add_view(
849 UsersView,
850 attr='groups_management',
851 route_name='edit_user_groups_management', request_method='GET',
852 renderer='rhodecode:templates/admin/users/user_edit.mako')
378 853
379 854 config.add_route(
380 855 name='edit_user_groups_management_updates',
381 856 pattern='/users/{user_id:\d+}/edit/edit_user_groups_management/updates',
382 857 user_route=True)
858 config.add_view(
859 UsersView,
860 attr='groups_management_updates',
861 route_name='edit_user_groups_management_updates', request_method='POST')
383 862
384 863 # user audit logs
385 864 config.add_route(
386 865 name='edit_user_audit_logs',
387 866 pattern='/users/{user_id:\d+}/edit/audit', user_route=True)
867 config.add_view(
868 UsersView,
869 attr='user_audit_logs',
870 route_name='edit_user_audit_logs', request_method='GET',
871 renderer='rhodecode:templates/admin/users/user_edit.mako')
388 872
389 873 config.add_route(
390 874 name='edit_user_audit_logs_download',
391 875 pattern='/users/{user_id:\d+}/edit/audit/download', user_route=True)
876 config.add_view(
877 UsersView,
878 attr='user_audit_logs_download',
879 route_name='edit_user_audit_logs_download', request_method='GET',
880 renderer='string')
392 881
393 882 # user caches
394 883 config.add_route(
395 884 name='edit_user_caches',
396 885 pattern='/users/{user_id:\d+}/edit/caches',
397 886 user_route=True)
887 config.add_view(
888 UsersView,
889 attr='user_caches',
890 route_name='edit_user_caches', request_method='GET',
891 renderer='rhodecode:templates/admin/users/user_edit.mako')
892
398 893 config.add_route(
399 894 name='edit_user_caches_update',
400 895 pattern='/users/{user_id:\d+}/edit/caches/update',
401 896 user_route=True)
897 config.add_view(
898 UsersView,
899 attr='user_caches_update',
900 route_name='edit_user_caches_update', request_method='POST')
402 901
403 902 # user-groups admin
404 903 config.add_route(
405 904 name='user_groups',
406 905 pattern='/user_groups')
906 config.add_view(
907 AdminUserGroupsView,
908 attr='user_groups_list',
909 route_name='user_groups', request_method='GET',
910 renderer='rhodecode:templates/admin/user_groups/user_groups.mako')
407 911
408 912 config.add_route(
409 913 name='user_groups_data',
410 914 pattern='/user_groups_data')
915 config.add_view(
916 AdminUserGroupsView,
917 attr='user_groups_list_data',
918 route_name='user_groups_data', request_method='GET',
919 renderer='json_ext', xhr=True)
411 920
412 921 config.add_route(
413 922 name='user_groups_new',
414 923 pattern='/user_groups/new')
924 config.add_view(
925 AdminUserGroupsView,
926 attr='user_groups_new',
927 route_name='user_groups_new', request_method='GET',
928 renderer='rhodecode:templates/admin/user_groups/user_group_add.mako')
415 929
416 930 config.add_route(
417 931 name='user_groups_create',
418 932 pattern='/user_groups/create')
933 config.add_view(
934 AdminUserGroupsView,
935 attr='user_groups_create',
936 route_name='user_groups_create', request_method='POST',
937 renderer='rhodecode:templates/admin/user_groups/user_group_add.mako')
419 938
420 939 # repos admin
421 940 config.add_route(
422 941 name='repos',
423 942 pattern='/repos')
943 config.add_view(
944 AdminReposView,
945 attr='repository_list',
946 route_name='repos', request_method='GET',
947 renderer='rhodecode:templates/admin/repos/repos.mako')
424 948
425 949 config.add_route(
426 950 name='repos_data',
427 951 pattern='/repos_data')
952 config.add_view(
953 AdminReposView,
954 attr='repository_list_data',
955 route_name='repos_data', request_method='GET',
956 renderer='json_ext', xhr=True)
428 957
429 958 config.add_route(
430 959 name='repo_new',
431 960 pattern='/repos/new')
961 config.add_view(
962 AdminReposView,
963 attr='repository_new',
964 route_name='repo_new', request_method='GET',
965 renderer='rhodecode:templates/admin/repos/repo_add.mako')
432 966
433 967 config.add_route(
434 968 name='repo_create',
435 969 pattern='/repos/create')
970 config.add_view(
971 AdminReposView,
972 attr='repository_create',
973 route_name='repo_create', request_method='POST',
974 renderer='rhodecode:templates/admin/repos/repos.mako')
436 975
437 976 # repo groups admin
438 977 config.add_route(
439 978 name='repo_groups',
440 979 pattern='/repo_groups')
980 config.add_view(
981 AdminRepoGroupsView,
982 attr='repo_group_list',
983 route_name='repo_groups', request_method='GET',
984 renderer='rhodecode:templates/admin/repo_groups/repo_groups.mako')
441 985
442 986 config.add_route(
443 987 name='repo_groups_data',
444 988 pattern='/repo_groups_data')
989 config.add_view(
990 AdminRepoGroupsView,
991 attr='repo_group_list_data',
992 route_name='repo_groups_data', request_method='GET',
993 renderer='json_ext', xhr=True)
445 994
446 995 config.add_route(
447 996 name='repo_group_new',
448 997 pattern='/repo_group/new')
998 config.add_view(
999 AdminRepoGroupsView,
1000 attr='repo_group_new',
1001 route_name='repo_group_new', request_method='GET',
1002 renderer='rhodecode:templates/admin/repo_groups/repo_group_add.mako')
449 1003
450 1004 config.add_route(
451 1005 name='repo_group_create',
452 1006 pattern='/repo_group/create')
1007 config.add_view(
1008 AdminRepoGroupsView,
1009 attr='repo_group_create',
1010 route_name='repo_group_create', request_method='POST',
1011 renderer='rhodecode:templates/admin/repo_groups/repo_group_add.mako')
453 1012
454 1013
455 1014 def includeme(config):
456 1015 from rhodecode.apps._base.navigation import includeme as nav_includeme
1016 from rhodecode.apps.admin.views.main_views import AdminMainView
457 1017
458 1018 # Create admin navigation registry and add it to the pyramid registry.
459 1019 nav_includeme(config)
460 1020
461 1021 # main admin routes
462 config.add_route(name='admin_home', pattern=ADMIN_PREFIX)
463 config.include(admin_routes, route_prefix=ADMIN_PREFIX)
1022 config.add_route(
1023 name='admin_home', pattern=ADMIN_PREFIX)
1024 config.add_view(
1025 AdminMainView,
1026 attr='admin_main',
1027 route_name='admin_home', request_method='GET',
1028 renderer='rhodecode:templates/admin/main.mako')
1029
1030 # pr global redirect
1031 config.add_route(
1032 name='pull_requests_global_0', # backward compat
1033 pattern=ADMIN_PREFIX + '/pull_requests/{pull_request_id:\d+}')
1034 config.add_view(
1035 AdminMainView,
1036 attr='pull_requests',
1037 route_name='pull_requests_global_0', request_method='GET')
464 1038
465 # Scan module for configuration decorators.
466 config.scan('.views', ignore='.tests')
1039 config.add_route(
1040 name='pull_requests_global_1', # backward compat
1041 pattern=ADMIN_PREFIX + '/pull-requests/{pull_request_id:\d+}')
1042 config.add_view(
1043 AdminMainView,
1044 attr='pull_requests',
1045 route_name='pull_requests_global_1', request_method='GET')
1046
1047 config.add_route(
1048 name='pull_requests_global',
1049 pattern=ADMIN_PREFIX + '/pull-request/{pull_request_id:\d+}')
1050 config.add_view(
1051 AdminMainView,
1052 attr='pull_requests',
1053 route_name='pull_requests_global', request_method='GET')
1054
1055 config.include(admin_routes, route_prefix=ADMIN_PREFIX)
@@ -1,93 +1,87 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2020 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 from pyramid.view import view_config
25 24
26 25 from rhodecode.apps._base import BaseAppView
27 26 from rhodecode.model.db import joinedload, UserLog
28 27 from rhodecode.lib.user_log_filter import user_log_filter
29 28 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
30 29 from rhodecode.lib.utils2 import safe_int
31 30 from rhodecode.lib.helpers import SqlPage
32 31
33 32 log = logging.getLogger(__name__)
34 33
35 34
36 35 class AdminAuditLogsView(BaseAppView):
36
37 37 def load_default_context(self):
38 38 c = self._get_local_tmpl_context()
39 39 return c
40 40
41 41 @LoginRequired()
42 42 @HasPermissionAllDecorator('hg.admin')
43 @view_config(
44 route_name='admin_audit_logs', request_method='GET',
45 renderer='rhodecode:templates/admin/admin_audit_logs.mako')
46 43 def admin_audit_logs(self):
47 44 c = self.load_default_context()
48 45
49 46 users_log = UserLog.query()\
50 47 .options(joinedload(UserLog.user))\
51 48 .options(joinedload(UserLog.repository))
52 49
53 50 # FILTERING
54 51 c.search_term = self.request.GET.get('filter')
55 52 try:
56 53 users_log = user_log_filter(users_log, c.search_term)
57 54 except Exception:
58 55 # we want this to crash for now
59 56 raise
60 57
61 58 users_log = users_log.order_by(UserLog.action_date.desc())
62 59
63 60 p = safe_int(self.request.GET.get('page', 1), 1)
64 61
65 62 def url_generator(page_num):
66 63 query_params = {
67 64 'page': page_num
68 65 }
69 66 if c.search_term:
70 67 query_params['filter'] = c.search_term
71 68 return self.request.current_route_path(_query=query_params)
72 69
73 70 c.audit_logs = SqlPage(users_log, page=p, items_per_page=10,
74 71 url_maker=url_generator)
75 72 return self._get_template_context(c)
76 73
77 74 @LoginRequired()
78 75 @HasPermissionAllDecorator('hg.admin')
79 @view_config(
80 route_name='admin_audit_log_entry', request_method='GET',
81 renderer='rhodecode:templates/admin/admin_audit_log_entry.mako')
82 76 def admin_audit_log_entry(self):
83 77 c = self.load_default_context()
84 78 audit_log_id = self.request.matchdict['audit_log_id']
85 79
86 80 c.audit_log_entry = UserLog.query()\
87 81 .options(joinedload(UserLog.user))\
88 82 .options(joinedload(UserLog.repository))\
89 83 .filter(UserLog.user_log_id == audit_log_id).scalar()
90 84 if not c.audit_log_entry:
91 85 raise HTTPNotFound()
92 86
93 87 return self._get_template_context(c)
@@ -1,111 +1,103 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2020 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 from pyramid.view import view_config
27 26 from pyramid.httpexceptions import HTTPFound
28 27 from pyramid.renderers import render
29 28 from pyramid.response import Response
30 29
31 30 from rhodecode.apps._base import BaseAppView
32 31 from rhodecode.lib.auth import (
33 32 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
34 33 from rhodecode.lib import helpers as h
35 34 from rhodecode.model.forms import DefaultsForm
36 35 from rhodecode.model.meta import Session
37 36 from rhodecode import BACKENDS
38 37 from rhodecode.model.settings import SettingsModel
39 38
40 39 log = logging.getLogger(__name__)
41 40
42 41
43 42 class AdminDefaultSettingsView(BaseAppView):
43
44 44 def load_default_context(self):
45 45 c = self._get_local_tmpl_context()
46
47
48 46 return c
49 47
50 48 @LoginRequired()
51 49 @HasPermissionAllDecorator('hg.admin')
52 @view_config(
53 route_name='admin_defaults_repositories', request_method='GET',
54 renderer='rhodecode:templates/admin/defaults/defaults.mako')
55 50 def defaults_repository_show(self):
56 51 c = self.load_default_context()
57 52 c.backends = BACKENDS.keys()
58 53 c.active = 'repositories'
59 54 defaults = SettingsModel().get_default_repo_settings()
60 55
61 56 data = render(
62 57 'rhodecode:templates/admin/defaults/defaults.mako',
63 58 self._get_template_context(c), self.request)
64 59 html = formencode.htmlfill.render(
65 60 data,
66 61 defaults=defaults,
67 62 encoding="UTF-8",
68 63 force_defaults=False
69 64 )
70 65 return Response(html)
71 66
72 67 @LoginRequired()
73 68 @HasPermissionAllDecorator('hg.admin')
74 69 @CSRFRequired()
75 @view_config(
76 route_name='admin_defaults_repositories_update', request_method='POST',
77 renderer='rhodecode:templates/admin/defaults/defaults.mako')
78 70 def defaults_repository_update(self):
79 71 _ = self.request.translate
80 72 c = self.load_default_context()
81 73 c.active = 'repositories'
82 74 form = DefaultsForm(self.request.translate)()
83 75
84 76 try:
85 77 form_result = form.to_python(dict(self.request.POST))
86 78 for k, v in form_result.iteritems():
87 79 setting = SettingsModel().create_or_update_setting(k, v)
88 80 Session().add(setting)
89 81 Session().commit()
90 82 h.flash(_('Default settings updated successfully'),
91 83 category='success')
92 84
93 85 except formencode.Invalid as errors:
94 86 data = render(
95 87 'rhodecode:templates/admin/defaults/defaults.mako',
96 88 self._get_template_context(c), self.request)
97 89 html = formencode.htmlfill.render(
98 90 data,
99 91 defaults=errors.value,
100 92 errors=errors.error_dict or {},
101 93 prefix_error=False,
102 94 encoding="UTF-8",
103 95 force_defaults=False
104 96 )
105 97 return Response(html)
106 98 except Exception:
107 99 log.exception('Exception in update action')
108 100 h.flash(_('Error occurred during update of default values'),
109 101 category='error')
110 102
111 103 raise HTTPFound(h.route_path('admin_defaults_repositories'))
@@ -1,174 +1,161 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2018-2020 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 import os
21 21 import logging
22 22
23 23 from pyramid.httpexceptions import HTTPFound
24 from pyramid.view import view_config
25 24
26 25 from rhodecode.apps._base import BaseAppView
27 26 from rhodecode.apps._base.navigation import navigation_list
28 27 from rhodecode.lib import helpers as h
29 28 from rhodecode.lib.auth import (
30 29 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
31 30 from rhodecode.lib.utils2 import time_to_utcdatetime, safe_int
32 31 from rhodecode.lib import exc_tracking
33 32
34 33 log = logging.getLogger(__name__)
35 34
36 35
37 36 class ExceptionsTrackerView(BaseAppView):
38 37 def load_default_context(self):
39 38 c = self._get_local_tmpl_context()
40 39 c.navlist = navigation_list(self.request)
41 40 return c
42 41
43 42 def count_all_exceptions(self):
44 43 exc_store_path = exc_tracking.get_exc_store()
45 44 count = 0
46 45 for fname in os.listdir(exc_store_path):
47 46 parts = fname.split('_', 2)
48 47 if not len(parts) == 3:
49 48 continue
50 49 count +=1
51 50 return count
52 51
53 52 def get_all_exceptions(self, read_metadata=False, limit=None, type_filter=None):
54 53 exc_store_path = exc_tracking.get_exc_store()
55 54 exception_list = []
56 55
57 56 def key_sorter(val):
58 57 try:
59 58 return val.split('_')[-1]
60 59 except Exception:
61 60 return 0
62 61
63 62 for fname in reversed(sorted(os.listdir(exc_store_path), key=key_sorter)):
64 63
65 64 parts = fname.split('_', 2)
66 65 if not len(parts) == 3:
67 66 continue
68 67
69 68 exc_id, app_type, exc_timestamp = parts
70 69
71 70 exc = {'exc_id': exc_id, 'app_type': app_type, 'exc_type': 'unknown',
72 71 'exc_utc_date': '', 'exc_timestamp': exc_timestamp}
73 72
74 73 if read_metadata:
75 74 full_path = os.path.join(exc_store_path, fname)
76 75 if not os.path.isfile(full_path):
77 76 continue
78 77 try:
79 78 # we can read our metadata
80 79 with open(full_path, 'rb') as f:
81 80 exc_metadata = exc_tracking.exc_unserialize(f.read())
82 81 exc.update(exc_metadata)
83 82 except Exception:
84 83 log.exception('Failed to read exc data from:{}'.format(full_path))
85 84 pass
86 85 # convert our timestamp to a date obj, for nicer representation
87 86 exc['exc_utc_date'] = time_to_utcdatetime(exc['exc_timestamp'])
88 87
89 88 type_present = exc.get('exc_type')
90 89 if type_filter:
91 90 if type_present and type_present == type_filter:
92 91 exception_list.append(exc)
93 92 else:
94 93 exception_list.append(exc)
95 94
96 95 if limit and len(exception_list) >= limit:
97 96 break
98 97 return exception_list
99 98
100 99 @LoginRequired()
101 100 @HasPermissionAllDecorator('hg.admin')
102 @view_config(
103 route_name='admin_settings_exception_tracker', request_method='GET',
104 renderer='rhodecode:templates/admin/settings/settings.mako')
105 101 def browse_exceptions(self):
106 102 _ = self.request.translate
107 103 c = self.load_default_context()
108 104 c.active = 'exceptions_browse'
109 105 c.limit = safe_int(self.request.GET.get('limit')) or 50
110 106 c.type_filter = self.request.GET.get('type_filter')
111 107 c.next_limit = c.limit + 50
112 108 c.exception_list = self.get_all_exceptions(
113 109 read_metadata=True, limit=c.limit, type_filter=c.type_filter)
114 110 c.exception_list_count = self.count_all_exceptions()
115 111 c.exception_store_dir = exc_tracking.get_exc_store()
116 112 return self._get_template_context(c)
117 113
118 114 @LoginRequired()
119 115 @HasPermissionAllDecorator('hg.admin')
120 @view_config(
121 route_name='admin_settings_exception_tracker_show', request_method='GET',
122 renderer='rhodecode:templates/admin/settings/settings.mako')
123 116 def exception_show(self):
124 117 _ = self.request.translate
125 118 c = self.load_default_context()
126 119
127 120 c.active = 'exceptions'
128 121 c.exception_id = self.request.matchdict['exception_id']
129 122 c.traceback = exc_tracking.read_exception(c.exception_id, prefix=None)
130 123 return self._get_template_context(c)
131 124
132 125 @LoginRequired()
133 126 @HasPermissionAllDecorator('hg.admin')
134 127 @CSRFRequired()
135 @view_config(
136 route_name='admin_settings_exception_tracker_delete_all', request_method='POST',
137 renderer='rhodecode:templates/admin/settings/settings.mako')
138 128 def exception_delete_all(self):
139 129 _ = self.request.translate
140 130 c = self.load_default_context()
141 131 type_filter = self.request.POST.get('type_filter')
142 132
143 133 c.active = 'exceptions'
144 134 all_exc = self.get_all_exceptions(read_metadata=bool(type_filter), type_filter=type_filter)
145 135 exc_count = 0
146 136
147 137 for exc in all_exc:
148 138 if type_filter:
149 139 if exc.get('exc_type') == type_filter:
150 140 exc_tracking.delete_exception(exc['exc_id'], prefix=None)
151 141 exc_count += 1
152 142 else:
153 143 exc_tracking.delete_exception(exc['exc_id'], prefix=None)
154 144 exc_count += 1
155 145
156 146 h.flash(_('Removed {} Exceptions').format(exc_count), category='success')
157 147 raise HTTPFound(h.route_path('admin_settings_exception_tracker'))
158 148
159 149 @LoginRequired()
160 150 @HasPermissionAllDecorator('hg.admin')
161 151 @CSRFRequired()
162 @view_config(
163 route_name='admin_settings_exception_tracker_delete', request_method='POST',
164 renderer='rhodecode:templates/admin/settings/settings.mako')
165 152 def exception_delete(self):
166 153 _ = self.request.translate
167 154 c = self.load_default_context()
168 155
169 156 c.active = 'exceptions'
170 157 c.exception_id = self.request.matchdict['exception_id']
171 158 exc_tracking.delete_exception(c.exception_id, prefix=None)
172 159
173 160 h.flash(_('Removed Exception {}').format(c.exception_id), category='success')
174 161 raise HTTPFound(h.route_path('admin_settings_exception_tracker'))
@@ -1,79 +1,72 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2020 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, HTTPNotFound
24 from pyramid.view import view_config
25 24
26 25 from rhodecode.apps._base import BaseAppView
27 26 from rhodecode.lib import helpers as h
28 27 from rhodecode.lib.auth import (LoginRequired, NotAnonymous, HasRepoPermissionAny)
29 28 from rhodecode.model.db import PullRequest
30 29
31 30
32 31 log = logging.getLogger(__name__)
33 32
34 33
35 34 class AdminMainView(BaseAppView):
36 35 def load_default_context(self):
37 36 c = self._get_local_tmpl_context()
38 37 return c
39 38
40 39 @LoginRequired()
41 40 @NotAnonymous()
42 @view_config(
43 route_name='admin_home', request_method='GET',
44 renderer='rhodecode:templates/admin/main.mako')
45 41 def admin_main(self):
46 42 c = self.load_default_context()
47 43 c.active = 'admin'
48 44
49 45 if not (c.is_super_admin or c.is_delegated_admin):
50 46 raise HTTPNotFound()
51 47
52 48 return self._get_template_context(c)
53 49
54 50 @LoginRequired()
55 @view_config(route_name='pull_requests_global_0', request_method='GET')
56 @view_config(route_name='pull_requests_global_1', request_method='GET')
57 @view_config(route_name='pull_requests_global', request_method='GET')
58 51 def pull_requests(self):
59 52 """
60 53 Global redirect for Pull Requests
61 54 pull_request_id: id of pull requests in the system
62 55 """
63 56
64 57 pull_request = PullRequest.get_or_404(
65 58 self.request.matchdict['pull_request_id'])
66 59 pull_request_id = pull_request.pull_request_id
67 60
68 61 repo_name = pull_request.target_repo.repo_name
69 62 # NOTE(marcink):
70 63 # check permissions so we don't redirect to repo that we don't have access to
71 64 # exposing it's name
72 65 target_repo_perm = HasRepoPermissionAny(
73 66 'repository.read', 'repository.write', 'repository.admin')(repo_name)
74 67 if not target_repo_perm:
75 68 raise HTTPNotFound()
76 69
77 70 raise HTTPFound(
78 71 h.route_path('pullrequest_show', repo_name=repo_name,
79 72 pull_request_id=pull_request_id))
@@ -1,51 +1,46 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2020 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 from pyramid.view import view_config
25
26 24 from rhodecode.apps._base import BaseAppView
27 25 from rhodecode.apps._base.navigation import navigation_list
28 26 from rhodecode.lib.auth import (LoginRequired, HasPermissionAllDecorator)
29 27 from rhodecode.lib.utils import read_opensource_licenses
30 28
31 29 log = logging.getLogger(__name__)
32 30
33 31
34 32 class OpenSourceLicensesAdminSettingsView(BaseAppView):
35 33
36 34 def load_default_context(self):
37 35 c = self._get_local_tmpl_context()
38 36 return c
39 37
40 38 @LoginRequired()
41 39 @HasPermissionAllDecorator('hg.admin')
42 @view_config(
43 route_name='admin_settings_open_source', request_method='GET',
44 renderer='rhodecode:templates/admin/settings/settings.mako')
45 40 def open_source_licenses(self):
46 41 c = self.load_default_context()
47 42 c.active = 'open_source'
48 43 c.navlist = navigation_list(self.request)
49 44 c.opensource_licenses = sorted(
50 45 read_opensource_licenses(), key=lambda d: d["name"])
51 46 return self._get_template_context(c)
@@ -1,519 +1,479 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2020 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 from pyramid.view import view_config
29 28 from pyramid.httpexceptions import HTTPFound
30 29 from pyramid.renderers import render
31 30 from pyramid.response import Response
32 31
33 32 from rhodecode.apps._base import BaseAppView, DataGridAppView
34 33 from rhodecode.apps.ssh_support import SshKeyFileChangeEvent
35 34 from rhodecode import events
36 35
37 36 from rhodecode.lib import helpers as h
38 37 from rhodecode.lib.auth import (
39 38 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
40 39 from rhodecode.lib.utils2 import aslist, safe_unicode
41 40 from rhodecode.model.db import (
42 41 or_, coalesce, User, UserIpMap, UserSshKeys)
43 42 from rhodecode.model.forms import (
44 43 ApplicationPermissionsForm, ObjectPermissionsForm, UserPermissionsForm)
45 44 from rhodecode.model.meta import Session
46 45 from rhodecode.model.permission import PermissionModel
47 46 from rhodecode.model.settings import SettingsModel
48 47
49 48
50 49 log = logging.getLogger(__name__)
51 50
52 51
53 52 class AdminPermissionsView(BaseAppView, DataGridAppView):
54 53 def load_default_context(self):
55 54 c = self._get_local_tmpl_context()
56 55 PermissionModel().set_global_permission_choices(
57 56 c, gettext_translator=self.request.translate)
58 57 return c
59 58
60 59 @LoginRequired()
61 60 @HasPermissionAllDecorator('hg.admin')
62 @view_config(
63 route_name='admin_permissions_application', request_method='GET',
64 renderer='rhodecode:templates/admin/permissions/permissions.mako')
65 61 def permissions_application(self):
66 62 c = self.load_default_context()
67 63 c.active = 'application'
68 64
69 65 c.user = User.get_default_user(refresh=True)
70 66
71 67 app_settings = c.rc_config
72 68
73 69 defaults = {
74 70 'anonymous': c.user.active,
75 71 'default_register_message': app_settings.get(
76 72 'rhodecode_register_message')
77 73 }
78 74 defaults.update(c.user.get_default_perms())
79 75
80 76 data = render('rhodecode:templates/admin/permissions/permissions.mako',
81 77 self._get_template_context(c), self.request)
82 78 html = formencode.htmlfill.render(
83 79 data,
84 80 defaults=defaults,
85 81 encoding="UTF-8",
86 82 force_defaults=False
87 83 )
88 84 return Response(html)
89 85
90 86 @LoginRequired()
91 87 @HasPermissionAllDecorator('hg.admin')
92 88 @CSRFRequired()
93 @view_config(
94 route_name='admin_permissions_application_update', request_method='POST',
95 renderer='rhodecode:templates/admin/permissions/permissions.mako')
96 89 def permissions_application_update(self):
97 90 _ = self.request.translate
98 91 c = self.load_default_context()
99 92 c.active = 'application'
100 93
101 94 _form = ApplicationPermissionsForm(
102 95 self.request.translate,
103 96 [x[0] for x in c.register_choices],
104 97 [x[0] for x in c.password_reset_choices],
105 98 [x[0] for x in c.extern_activate_choices])()
106 99
107 100 try:
108 101 form_result = _form.to_python(dict(self.request.POST))
109 102 form_result.update({'perm_user_name': User.DEFAULT_USER})
110 103 PermissionModel().update_application_permissions(form_result)
111 104
112 105 settings = [
113 106 ('register_message', 'default_register_message'),
114 107 ]
115 108 for setting, form_key in settings:
116 109 sett = SettingsModel().create_or_update_setting(
117 110 setting, form_result[form_key])
118 111 Session().add(sett)
119 112
120 113 Session().commit()
121 114 h.flash(_('Application permissions updated successfully'),
122 115 category='success')
123 116
124 117 except formencode.Invalid as errors:
125 118 defaults = errors.value
126 119
127 120 data = render(
128 121 'rhodecode:templates/admin/permissions/permissions.mako',
129 122 self._get_template_context(c), self.request)
130 123 html = formencode.htmlfill.render(
131 124 data,
132 125 defaults=defaults,
133 126 errors=errors.error_dict or {},
134 127 prefix_error=False,
135 128 encoding="UTF-8",
136 129 force_defaults=False
137 130 )
138 131 return Response(html)
139 132
140 133 except Exception:
141 134 log.exception("Exception during update of permissions")
142 135 h.flash(_('Error occurred during update of permissions'),
143 136 category='error')
144 137
145 138 affected_user_ids = [User.get_default_user_id()]
146 139 PermissionModel().trigger_permission_flush(affected_user_ids)
147 140
148 141 raise HTTPFound(h.route_path('admin_permissions_application'))
149 142
150 143 @LoginRequired()
151 144 @HasPermissionAllDecorator('hg.admin')
152 @view_config(
153 route_name='admin_permissions_object', request_method='GET',
154 renderer='rhodecode:templates/admin/permissions/permissions.mako')
155 145 def permissions_objects(self):
156 146 c = self.load_default_context()
157 147 c.active = 'objects'
158 148
159 149 c.user = User.get_default_user(refresh=True)
160 150 defaults = {}
161 151 defaults.update(c.user.get_default_perms())
162 152
163 153 data = render(
164 154 'rhodecode:templates/admin/permissions/permissions.mako',
165 155 self._get_template_context(c), self.request)
166 156 html = formencode.htmlfill.render(
167 157 data,
168 158 defaults=defaults,
169 159 encoding="UTF-8",
170 160 force_defaults=False
171 161 )
172 162 return Response(html)
173 163
174 164 @LoginRequired()
175 165 @HasPermissionAllDecorator('hg.admin')
176 166 @CSRFRequired()
177 @view_config(
178 route_name='admin_permissions_object_update', request_method='POST',
179 renderer='rhodecode:templates/admin/permissions/permissions.mako')
180 167 def permissions_objects_update(self):
181 168 _ = self.request.translate
182 169 c = self.load_default_context()
183 170 c.active = 'objects'
184 171
185 172 _form = ObjectPermissionsForm(
186 173 self.request.translate,
187 174 [x[0] for x in c.repo_perms_choices],
188 175 [x[0] for x in c.group_perms_choices],
189 176 [x[0] for x in c.user_group_perms_choices],
190 177 )()
191 178
192 179 try:
193 180 form_result = _form.to_python(dict(self.request.POST))
194 181 form_result.update({'perm_user_name': User.DEFAULT_USER})
195 182 PermissionModel().update_object_permissions(form_result)
196 183
197 184 Session().commit()
198 185 h.flash(_('Object permissions updated successfully'),
199 186 category='success')
200 187
201 188 except formencode.Invalid as errors:
202 189 defaults = errors.value
203 190
204 191 data = render(
205 192 'rhodecode:templates/admin/permissions/permissions.mako',
206 193 self._get_template_context(c), self.request)
207 194 html = formencode.htmlfill.render(
208 195 data,
209 196 defaults=defaults,
210 197 errors=errors.error_dict or {},
211 198 prefix_error=False,
212 199 encoding="UTF-8",
213 200 force_defaults=False
214 201 )
215 202 return Response(html)
216 203 except Exception:
217 204 log.exception("Exception during update of permissions")
218 205 h.flash(_('Error occurred during update of permissions'),
219 206 category='error')
220 207
221 208 affected_user_ids = [User.get_default_user_id()]
222 209 PermissionModel().trigger_permission_flush(affected_user_ids)
223 210
224 211 raise HTTPFound(h.route_path('admin_permissions_object'))
225 212
226 213 @LoginRequired()
227 214 @HasPermissionAllDecorator('hg.admin')
228 @view_config(
229 route_name='admin_permissions_branch', request_method='GET',
230 renderer='rhodecode:templates/admin/permissions/permissions.mako')
231 215 def permissions_branch(self):
232 216 c = self.load_default_context()
233 217 c.active = 'branch'
234 218
235 219 c.user = User.get_default_user(refresh=True)
236 220 defaults = {}
237 221 defaults.update(c.user.get_default_perms())
238 222
239 223 data = render(
240 224 'rhodecode:templates/admin/permissions/permissions.mako',
241 225 self._get_template_context(c), self.request)
242 226 html = formencode.htmlfill.render(
243 227 data,
244 228 defaults=defaults,
245 229 encoding="UTF-8",
246 230 force_defaults=False
247 231 )
248 232 return Response(html)
249 233
250 234 @LoginRequired()
251 235 @HasPermissionAllDecorator('hg.admin')
252 @view_config(
253 route_name='admin_permissions_global', request_method='GET',
254 renderer='rhodecode:templates/admin/permissions/permissions.mako')
255 236 def permissions_global(self):
256 237 c = self.load_default_context()
257 238 c.active = 'global'
258 239
259 240 c.user = User.get_default_user(refresh=True)
260 241 defaults = {}
261 242 defaults.update(c.user.get_default_perms())
262 243
263 244 data = render(
264 245 'rhodecode:templates/admin/permissions/permissions.mako',
265 246 self._get_template_context(c), self.request)
266 247 html = formencode.htmlfill.render(
267 248 data,
268 249 defaults=defaults,
269 250 encoding="UTF-8",
270 251 force_defaults=False
271 252 )
272 253 return Response(html)
273 254
274 255 @LoginRequired()
275 256 @HasPermissionAllDecorator('hg.admin')
276 257 @CSRFRequired()
277 @view_config(
278 route_name='admin_permissions_global_update', request_method='POST',
279 renderer='rhodecode:templates/admin/permissions/permissions.mako')
280 258 def permissions_global_update(self):
281 259 _ = self.request.translate
282 260 c = self.load_default_context()
283 261 c.active = 'global'
284 262
285 263 _form = UserPermissionsForm(
286 264 self.request.translate,
287 265 [x[0] for x in c.repo_create_choices],
288 266 [x[0] for x in c.repo_create_on_write_choices],
289 267 [x[0] for x in c.repo_group_create_choices],
290 268 [x[0] for x in c.user_group_create_choices],
291 269 [x[0] for x in c.fork_choices],
292 270 [x[0] for x in c.inherit_default_permission_choices])()
293 271
294 272 try:
295 273 form_result = _form.to_python(dict(self.request.POST))
296 274 form_result.update({'perm_user_name': User.DEFAULT_USER})
297 275 PermissionModel().update_user_permissions(form_result)
298 276
299 277 Session().commit()
300 278 h.flash(_('Global permissions updated successfully'),
301 279 category='success')
302 280
303 281 except formencode.Invalid as errors:
304 282 defaults = errors.value
305 283
306 284 data = render(
307 285 'rhodecode:templates/admin/permissions/permissions.mako',
308 286 self._get_template_context(c), self.request)
309 287 html = formencode.htmlfill.render(
310 288 data,
311 289 defaults=defaults,
312 290 errors=errors.error_dict or {},
313 291 prefix_error=False,
314 292 encoding="UTF-8",
315 293 force_defaults=False
316 294 )
317 295 return Response(html)
318 296 except Exception:
319 297 log.exception("Exception during update of permissions")
320 298 h.flash(_('Error occurred during update of permissions'),
321 299 category='error')
322 300
323 301 affected_user_ids = [User.get_default_user_id()]
324 302 PermissionModel().trigger_permission_flush(affected_user_ids)
325 303
326 304 raise HTTPFound(h.route_path('admin_permissions_global'))
327 305
328 306 @LoginRequired()
329 307 @HasPermissionAllDecorator('hg.admin')
330 @view_config(
331 route_name='admin_permissions_ips', request_method='GET',
332 renderer='rhodecode:templates/admin/permissions/permissions.mako')
333 308 def permissions_ips(self):
334 309 c = self.load_default_context()
335 310 c.active = 'ips'
336 311
337 312 c.user = User.get_default_user(refresh=True)
338 313 c.user_ip_map = (
339 314 UserIpMap.query().filter(UserIpMap.user == c.user).all())
340 315
341 316 return self._get_template_context(c)
342 317
343 318 @LoginRequired()
344 319 @HasPermissionAllDecorator('hg.admin')
345 @view_config(
346 route_name='admin_permissions_overview', request_method='GET',
347 renderer='rhodecode:templates/admin/permissions/permissions.mako')
348 320 def permissions_overview(self):
349 321 c = self.load_default_context()
350 322 c.active = 'perms'
351 323
352 324 c.user = User.get_default_user(refresh=True)
353 325 c.perm_user = c.user.AuthUser()
354 326 return self._get_template_context(c)
355 327
356 328 @LoginRequired()
357 329 @HasPermissionAllDecorator('hg.admin')
358 @view_config(
359 route_name='admin_permissions_auth_token_access', request_method='GET',
360 renderer='rhodecode:templates/admin/permissions/permissions.mako')
361 330 def auth_token_access(self):
362 331 from rhodecode import CONFIG
363 332
364 333 c = self.load_default_context()
365 334 c.active = 'auth_token_access'
366 335
367 336 c.user = User.get_default_user(refresh=True)
368 337 c.perm_user = c.user.AuthUser()
369 338
370 339 mapper = self.request.registry.queryUtility(IRoutesMapper)
371 340 c.view_data = []
372 341
373 342 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
374 343 introspector = self.request.registry.introspector
375 344
376 345 view_intr = {}
377 346 for view_data in introspector.get_category('views'):
378 347 intr = view_data['introspectable']
379 348
380 349 if 'route_name' in intr and intr['attr']:
381 350 view_intr[intr['route_name']] = '{}:{}'.format(
382 351 str(intr['derived_callable'].func_name), intr['attr']
383 352 )
384 353
385 354 c.whitelist_key = 'api_access_controllers_whitelist'
386 355 c.whitelist_file = CONFIG.get('__file__')
387 356 whitelist_views = aslist(
388 357 CONFIG.get(c.whitelist_key), sep=',')
389 358
390 359 for route_info in mapper.get_routes():
391 360 if not route_info.name.startswith('__'):
392 361 routepath = route_info.pattern
393 362
394 363 def replace(matchobj):
395 364 if matchobj.group(1):
396 365 return "{%s}" % matchobj.group(1).split(':')[0]
397 366 else:
398 367 return "{%s}" % matchobj.group(2)
399 368
400 369 routepath = _argument_prog.sub(replace, routepath)
401 370
402 371 if not routepath.startswith('/'):
403 372 routepath = '/' + routepath
404 373
405 374 view_fqn = view_intr.get(route_info.name, 'NOT AVAILABLE')
406 375 active = view_fqn in whitelist_views
407 376 c.view_data.append((route_info.name, view_fqn, routepath, active))
408 377
409 378 c.whitelist_views = whitelist_views
410 379 return self._get_template_context(c)
411 380
412 381 def ssh_enabled(self):
413 382 return self.request.registry.settings.get(
414 383 'ssh.generate_authorized_keyfile')
415 384
416 385 @LoginRequired()
417 386 @HasPermissionAllDecorator('hg.admin')
418 @view_config(
419 route_name='admin_permissions_ssh_keys', request_method='GET',
420 renderer='rhodecode:templates/admin/permissions/permissions.mako')
421 387 def ssh_keys(self):
422 388 c = self.load_default_context()
423 389 c.active = 'ssh_keys'
424 390 c.ssh_enabled = self.ssh_enabled()
425 391 return self._get_template_context(c)
426 392
427 393 @LoginRequired()
428 394 @HasPermissionAllDecorator('hg.admin')
429 @view_config(
430 route_name='admin_permissions_ssh_keys_data', request_method='GET',
431 renderer='json_ext', xhr=True)
432 395 def ssh_keys_data(self):
433 396 _ = self.request.translate
434 397 self.load_default_context()
435 398 column_map = {
436 399 'fingerprint': 'ssh_key_fingerprint',
437 400 'username': User.username
438 401 }
439 402 draw, start, limit = self._extract_chunk(self.request)
440 403 search_q, order_by, order_dir = self._extract_ordering(
441 404 self.request, column_map=column_map)
442 405
443 406 ssh_keys_data_total_count = UserSshKeys.query()\
444 407 .count()
445 408
446 409 # json generate
447 410 base_q = UserSshKeys.query().join(UserSshKeys.user)
448 411
449 412 if search_q:
450 413 like_expression = u'%{}%'.format(safe_unicode(search_q))
451 414 base_q = base_q.filter(or_(
452 415 User.username.ilike(like_expression),
453 416 UserSshKeys.ssh_key_fingerprint.ilike(like_expression),
454 417 ))
455 418
456 419 users_data_total_filtered_count = base_q.count()
457 420
458 421 sort_col = self._get_order_col(order_by, UserSshKeys)
459 422 if sort_col:
460 423 if order_dir == 'asc':
461 424 # handle null values properly to order by NULL last
462 425 if order_by in ['created_on']:
463 426 sort_col = coalesce(sort_col, datetime.date.max)
464 427 sort_col = sort_col.asc()
465 428 else:
466 429 # handle null values properly to order by NULL last
467 430 if order_by in ['created_on']:
468 431 sort_col = coalesce(sort_col, datetime.date.min)
469 432 sort_col = sort_col.desc()
470 433
471 434 base_q = base_q.order_by(sort_col)
472 435 base_q = base_q.offset(start).limit(limit)
473 436
474 437 ssh_keys = base_q.all()
475 438
476 439 ssh_keys_data = []
477 440 for ssh_key in ssh_keys:
478 441 ssh_keys_data.append({
479 442 "username": h.gravatar_with_user(self.request, ssh_key.user.username),
480 443 "fingerprint": ssh_key.ssh_key_fingerprint,
481 444 "description": ssh_key.description,
482 445 "created_on": h.format_date(ssh_key.created_on),
483 446 "accessed_on": h.format_date(ssh_key.accessed_on),
484 447 "action": h.link_to(
485 448 _('Edit'), h.route_path('edit_user_ssh_keys',
486 449 user_id=ssh_key.user.user_id))
487 450 })
488 451
489 452 data = ({
490 453 'draw': draw,
491 454 'data': ssh_keys_data,
492 455 'recordsTotal': ssh_keys_data_total_count,
493 456 'recordsFiltered': users_data_total_filtered_count,
494 457 })
495 458
496 459 return data
497 460
498 461 @LoginRequired()
499 462 @HasPermissionAllDecorator('hg.admin')
500 463 @CSRFRequired()
501 @view_config(
502 route_name='admin_permissions_ssh_keys_update', request_method='POST',
503 renderer='rhodecode:templates/admin/permissions/permissions.mako')
504 464 def ssh_keys_update(self):
505 465 _ = self.request.translate
506 466 self.load_default_context()
507 467
508 468 ssh_enabled = self.ssh_enabled()
509 469 key_file = self.request.registry.settings.get(
510 470 'ssh.authorized_keys_file_path')
511 471 if ssh_enabled:
512 472 events.trigger(SshKeyFileChangeEvent(), self.request.registry)
513 473 h.flash(_('Updated SSH keys file: {}').format(key_file),
514 474 category='success')
515 475 else:
516 476 h.flash(_('SSH key support is disabled in .ini file'),
517 477 category='warning')
518 478
519 479 raise HTTPFound(h.route_path('admin_permissions_ssh_keys'))
@@ -1,182 +1,170 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2020 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 import signal
25 from pyramid.view import view_config
25
26 26
27 27 from rhodecode.apps._base import BaseAppView
28 28 from rhodecode.apps._base.navigation import navigation_list
29 29 from rhodecode.lib import system_info
30 30 from rhodecode.lib.auth import (
31 31 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
32 32 from rhodecode.lib.utils2 import safe_int, StrictAttributeDict
33 33
34 34 log = logging.getLogger(__name__)
35 35
36 36
37 37 class AdminProcessManagementView(BaseAppView):
38 38 def load_default_context(self):
39 39 c = self._get_local_tmpl_context()
40 40 return c
41 41
42 42 def _format_proc(self, proc, with_children=False):
43 43 try:
44 44 mem = proc.memory_info()
45 45 proc_formatted = StrictAttributeDict({
46 46 'pid': proc.pid,
47 47 'name': proc.name(),
48 48 'mem_rss': mem.rss,
49 49 'mem_vms': mem.vms,
50 50 'cpu_percent': proc.cpu_percent(interval=0.1),
51 51 'create_time': proc.create_time(),
52 52 'cmd': ' '.join(proc.cmdline()),
53 53 })
54 54
55 55 if with_children:
56 56 proc_formatted.update({
57 57 'children': [self._format_proc(x)
58 58 for x in proc.children(recursive=True)]
59 59 })
60 60 except Exception:
61 61 log.exception('Failed to load proc')
62 62 proc_formatted = None
63 63 return proc_formatted
64 64
65 65 def get_processes(self):
66 66 proc_list = []
67 67 for p in psutil.process_iter():
68 68 if 'gunicorn' in p.name():
69 69 proc = self._format_proc(p, with_children=True)
70 70 if proc:
71 71 proc_list.append(proc)
72 72
73 73 return proc_list
74 74
75 75 def get_workers(self):
76 76 workers = None
77 77 try:
78 78 rc_config = system_info.rhodecode_config().value['config']
79 79 workers = rc_config['server:main'].get('workers')
80 80 except Exception:
81 81 pass
82 82
83 83 return workers or '?'
84 84
85 85 @LoginRequired()
86 86 @HasPermissionAllDecorator('hg.admin')
87 @view_config(
88 route_name='admin_settings_process_management', request_method='GET',
89 renderer='rhodecode:templates/admin/settings/settings.mako')
90 87 def process_management(self):
91 88 _ = self.request.translate
92 89 c = self.load_default_context()
93 90
94 91 c.active = 'process_management'
95 92 c.navlist = navigation_list(self.request)
96 93 c.gunicorn_processes = self.get_processes()
97 94 c.gunicorn_workers = self.get_workers()
98 95 return self._get_template_context(c)
99 96
100 97 @LoginRequired()
101 98 @HasPermissionAllDecorator('hg.admin')
102 @view_config(
103 route_name='admin_settings_process_management_data', request_method='GET',
104 renderer='rhodecode:templates/admin/settings/settings_process_management_data.mako')
105 99 def process_management_data(self):
106 100 _ = self.request.translate
107 101 c = self.load_default_context()
108 102 c.gunicorn_processes = self.get_processes()
109 103 return self._get_template_context(c)
110 104
111 105 @LoginRequired()
112 106 @HasPermissionAllDecorator('hg.admin')
113 107 @CSRFRequired()
114 @view_config(
115 route_name='admin_settings_process_management_signal',
116 request_method='POST', renderer='json_ext')
117 108 def process_management_signal(self):
118 109 pids = self.request.json.get('pids', [])
119 110 result = []
120 111
121 112 def on_terminate(proc):
122 113 msg = "terminated"
123 114 result.append(msg)
124 115
125 116 procs = []
126 117 for pid in pids:
127 118 pid = safe_int(pid)
128 119 if pid:
129 120 try:
130 121 proc = psutil.Process(pid)
131 122 except psutil.NoSuchProcess:
132 123 continue
133 124
134 125 children = proc.children(recursive=True)
135 126 if children:
136 127 log.warning('Wont kill Master Process')
137 128 else:
138 129 procs.append(proc)
139 130
140 131 for p in procs:
141 132 try:
142 133 p.terminate()
143 134 except psutil.AccessDenied as e:
144 135 log.warning('Access denied: {}'.format(e))
145 136
146 137 gone, alive = psutil.wait_procs(procs, timeout=10, callback=on_terminate)
147 138 for p in alive:
148 139 try:
149 140 p.kill()
150 141 except psutil.AccessDenied as e:
151 142 log.warning('Access denied: {}'.format(e))
152 143
153 144 return {'result': result}
154 145
155 146 @LoginRequired()
156 147 @HasPermissionAllDecorator('hg.admin')
157 148 @CSRFRequired()
158 @view_config(
159 route_name='admin_settings_process_management_master_signal',
160 request_method='POST', renderer='json_ext')
161 149 def process_management_master_signal(self):
162 150 pid_data = self.request.json.get('pid_data', {})
163 151 pid = safe_int(pid_data['pid'])
164 152 action = pid_data['action']
165 153 if pid:
166 154 try:
167 155 proc = psutil.Process(pid)
168 156 except psutil.NoSuchProcess:
169 157 return {'result': 'failure_no_such_process'}
170 158
171 159 children = proc.children(recursive=True)
172 160 if children:
173 161 # master process
174 162 if action == '+' and len(children) <= 20:
175 163 proc.send_signal(signal.SIGTTIN)
176 164 elif action == '-' and len(children) >= 2:
177 165 proc.send_signal(signal.SIGTTOU)
178 166 else:
179 167 return {'result': 'failure_wrong_action'}
180 168 return {'result': 'success'}
181 169
182 170 return {'result': 'failure_not_master'}
@@ -1,374 +1,362 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2020 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 import datetime
21 21 import logging
22 22 import time
23 23
24 24 import formencode
25 25 import formencode.htmlfill
26 26
27 27 from pyramid.httpexceptions import HTTPFound, HTTPForbidden
28 from pyramid.view import view_config
28
29 29 from pyramid.renderers import render
30 30 from pyramid.response import Response
31 31
32 32 from rhodecode import events
33 33 from rhodecode.apps._base import BaseAppView, DataGridAppView
34 34
35 35 from rhodecode.lib.auth import (
36 36 LoginRequired, CSRFRequired, NotAnonymous,
37 37 HasPermissionAny, HasRepoGroupPermissionAny)
38 38 from rhodecode.lib import helpers as h, audit_logger
39 39 from rhodecode.lib.utils2 import safe_int, safe_unicode, datetime_to_time
40 40 from rhodecode.model.forms import RepoGroupForm
41 41 from rhodecode.model.permission import PermissionModel
42 42 from rhodecode.model.repo_group import RepoGroupModel
43 43 from rhodecode.model.scm import RepoGroupList
44 44 from rhodecode.model.db import (
45 45 or_, count, func, in_filter_generator, Session, RepoGroup, User, Repository)
46 46
47 47 log = logging.getLogger(__name__)
48 48
49 49
50 50 class AdminRepoGroupsView(BaseAppView, DataGridAppView):
51 51
52 52 def load_default_context(self):
53 53 c = self._get_local_tmpl_context()
54 54
55 55 return c
56 56
57 57 def _load_form_data(self, c):
58 58 allow_empty_group = False
59 59
60 60 if self._can_create_repo_group():
61 61 # we're global admin, we're ok and we can create TOP level groups
62 62 allow_empty_group = True
63 63
64 64 # override the choices for this form, we need to filter choices
65 65 # and display only those we have ADMIN right
66 66 groups_with_admin_rights = RepoGroupList(
67 67 RepoGroup.query().all(),
68 68 perm_set=['group.admin'], extra_kwargs=dict(user=self._rhodecode_user))
69 69 c.repo_groups = RepoGroup.groups_choices(
70 70 groups=groups_with_admin_rights,
71 71 show_empty_group=allow_empty_group)
72 72 c.personal_repo_group = self._rhodecode_user.personal_repo_group
73 73
74 74 def _can_create_repo_group(self, parent_group_id=None):
75 75 is_admin = HasPermissionAny('hg.admin')('group create controller')
76 76 create_repo_group = HasPermissionAny(
77 77 'hg.repogroup.create.true')('group create controller')
78 78 if is_admin or (create_repo_group and not parent_group_id):
79 79 # we're global admin, or we have global repo group create
80 80 # permission
81 81 # we're ok and we can create TOP level groups
82 82 return True
83 83 elif parent_group_id:
84 84 # we check the permission if we can write to parent group
85 85 group = RepoGroup.get(parent_group_id)
86 86 group_name = group.group_name if group else None
87 87 if HasRepoGroupPermissionAny('group.admin')(
88 88 group_name, 'check if user is an admin of group'):
89 89 # we're an admin of passed in group, we're ok.
90 90 return True
91 91 else:
92 92 return False
93 93 return False
94 94
95 95 # permission check in data loading of
96 96 # `repo_group_list_data` via RepoGroupList
97 97 @LoginRequired()
98 98 @NotAnonymous()
99 @view_config(
100 route_name='repo_groups', request_method='GET',
101 renderer='rhodecode:templates/admin/repo_groups/repo_groups.mako')
102 99 def repo_group_list(self):
103 100 c = self.load_default_context()
104 101 return self._get_template_context(c)
105 102
106 103 # permission check inside
107 104 @LoginRequired()
108 105 @NotAnonymous()
109 @view_config(
110 route_name='repo_groups_data', request_method='GET',
111 renderer='json_ext', xhr=True)
112 106 def repo_group_list_data(self):
113 107 self.load_default_context()
114 108 column_map = {
115 109 'name': 'group_name_hash',
116 110 'desc': 'group_description',
117 111 'last_change': 'updated_on',
118 112 'top_level_repos': 'repos_total',
119 113 'owner': 'user_username',
120 114 }
121 115 draw, start, limit = self._extract_chunk(self.request)
122 116 search_q, order_by, order_dir = self._extract_ordering(
123 117 self.request, column_map=column_map)
124 118
125 119 _render = self.request.get_partial_renderer(
126 120 'rhodecode:templates/data_table/_dt_elements.mako')
127 121 c = _render.get_call_context()
128 122
129 123 def quick_menu(repo_group_name):
130 124 return _render('quick_repo_group_menu', repo_group_name)
131 125
132 126 def repo_group_lnk(repo_group_name):
133 127 return _render('repo_group_name', repo_group_name)
134 128
135 129 def last_change(last_change):
136 130 if isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
137 131 ts = time.time()
138 132 utc_offset = (datetime.datetime.fromtimestamp(ts)
139 133 - datetime.datetime.utcfromtimestamp(ts)).total_seconds()
140 134 last_change = last_change + datetime.timedelta(seconds=utc_offset)
141 135 return _render("last_change", last_change)
142 136
143 137 def desc(desc, personal):
144 138 return _render(
145 139 'repo_group_desc', desc, personal, c.visual.stylify_metatags)
146 140
147 141 def repo_group_actions(repo_group_id, repo_group_name, gr_count):
148 142 return _render(
149 143 'repo_group_actions', repo_group_id, repo_group_name, gr_count)
150 144
151 145 def user_profile(username):
152 146 return _render('user_profile', username)
153 147
154 148 _perms = ['group.admin']
155 149 allowed_ids = [-1] + self._rhodecode_user.repo_group_acl_ids_from_stack(_perms)
156 150
157 151 repo_groups_data_total_count = RepoGroup.query()\
158 152 .filter(or_(
159 153 # generate multiple IN to fix limitation problems
160 154 *in_filter_generator(RepoGroup.group_id, allowed_ids)
161 155 )) \
162 156 .count()
163 157
164 158 repo_groups_data_total_inactive_count = RepoGroup.query()\
165 159 .filter(RepoGroup.group_id.in_(allowed_ids))\
166 160 .count()
167 161
168 162 repo_count = count(Repository.repo_id)
169 163 base_q = Session.query(
170 164 RepoGroup.group_name,
171 165 RepoGroup.group_name_hash,
172 166 RepoGroup.group_description,
173 167 RepoGroup.group_id,
174 168 RepoGroup.personal,
175 169 RepoGroup.updated_on,
176 170 User,
177 171 repo_count.label('repos_count')
178 172 ) \
179 173 .filter(or_(
180 174 # generate multiple IN to fix limitation problems
181 175 *in_filter_generator(RepoGroup.group_id, allowed_ids)
182 176 )) \
183 177 .outerjoin(Repository, Repository.group_id == RepoGroup.group_id) \
184 178 .join(User, User.user_id == RepoGroup.user_id) \
185 179 .group_by(RepoGroup, User)
186 180
187 181 if search_q:
188 182 like_expression = u'%{}%'.format(safe_unicode(search_q))
189 183 base_q = base_q.filter(or_(
190 184 RepoGroup.group_name.ilike(like_expression),
191 185 ))
192 186
193 187 repo_groups_data_total_filtered_count = base_q.count()
194 188 # the inactive isn't really used, but we still make it same as other data grids
195 189 # which use inactive (users,user groups)
196 190 repo_groups_data_total_filtered_inactive_count = repo_groups_data_total_filtered_count
197 191
198 192 sort_defined = False
199 193 if order_by == 'group_name':
200 194 sort_col = func.lower(RepoGroup.group_name)
201 195 sort_defined = True
202 196 elif order_by == 'repos_total':
203 197 sort_col = repo_count
204 198 sort_defined = True
205 199 elif order_by == 'user_username':
206 200 sort_col = User.username
207 201 else:
208 202 sort_col = getattr(RepoGroup, order_by, None)
209 203
210 204 if sort_defined or sort_col:
211 205 if order_dir == 'asc':
212 206 sort_col = sort_col.asc()
213 207 else:
214 208 sort_col = sort_col.desc()
215 209
216 210 base_q = base_q.order_by(sort_col)
217 211 base_q = base_q.offset(start).limit(limit)
218 212
219 213 # authenticated access to user groups
220 214 auth_repo_group_list = base_q.all()
221 215
222 216 repo_groups_data = []
223 217 for repo_gr in auth_repo_group_list:
224 218 row = {
225 219 "menu": quick_menu(repo_gr.group_name),
226 220 "name": repo_group_lnk(repo_gr.group_name),
227 221
228 222 "last_change": last_change(repo_gr.updated_on),
229 223
230 224 "last_changeset": "",
231 225 "last_changeset_raw": "",
232 226
233 227 "desc": desc(repo_gr.group_description, repo_gr.personal),
234 228 "owner": user_profile(repo_gr.User.username),
235 229 "top_level_repos": repo_gr.repos_count,
236 230 "action": repo_group_actions(
237 231 repo_gr.group_id, repo_gr.group_name, repo_gr.repos_count),
238 232
239 233 }
240 234
241 235 repo_groups_data.append(row)
242 236
243 237 data = ({
244 238 'draw': draw,
245 239 'data': repo_groups_data,
246 240 'recordsTotal': repo_groups_data_total_count,
247 241 'recordsTotalInactive': repo_groups_data_total_inactive_count,
248 242 'recordsFiltered': repo_groups_data_total_filtered_count,
249 243 'recordsFilteredInactive': repo_groups_data_total_filtered_inactive_count,
250 244 })
251 245
252 246 return data
253 247
254 248 @LoginRequired()
255 249 @NotAnonymous()
256 250 # perm checks inside
257 @view_config(
258 route_name='repo_group_new', request_method='GET',
259 renderer='rhodecode:templates/admin/repo_groups/repo_group_add.mako')
260 251 def repo_group_new(self):
261 252 c = self.load_default_context()
262 253
263 254 # perm check for admin, create_group perm or admin of parent_group
264 255 parent_group_id = safe_int(self.request.GET.get('parent_group'))
265 256 _gr = RepoGroup.get(parent_group_id)
266 257 if not self._can_create_repo_group(parent_group_id):
267 258 raise HTTPForbidden()
268 259
269 260 self._load_form_data(c)
270 261
271 262 defaults = {} # Future proof for default of repo group
272 263
273 264 parent_group_choice = '-1'
274 265 if not self._rhodecode_user.is_admin and self._rhodecode_user.personal_repo_group:
275 266 parent_group_choice = self._rhodecode_user.personal_repo_group
276 267
277 268 if parent_group_id and _gr:
278 269 if parent_group_id in [x[0] for x in c.repo_groups]:
279 270 parent_group_choice = safe_unicode(parent_group_id)
280 271
281 272 defaults.update({'group_parent_id': parent_group_choice})
282 273
283 274 data = render(
284 275 'rhodecode:templates/admin/repo_groups/repo_group_add.mako',
285 276 self._get_template_context(c), self.request)
286 277
287 278 html = formencode.htmlfill.render(
288 279 data,
289 280 defaults=defaults,
290 281 encoding="UTF-8",
291 282 force_defaults=False
292 283 )
293 284 return Response(html)
294 285
295 286 @LoginRequired()
296 287 @NotAnonymous()
297 288 @CSRFRequired()
298 289 # perm checks inside
299 @view_config(
300 route_name='repo_group_create', request_method='POST',
301 renderer='rhodecode:templates/admin/repo_groups/repo_group_add.mako')
302 290 def repo_group_create(self):
303 291 c = self.load_default_context()
304 292 _ = self.request.translate
305 293
306 294 parent_group_id = safe_int(self.request.POST.get('group_parent_id'))
307 295 can_create = self._can_create_repo_group(parent_group_id)
308 296
309 297 self._load_form_data(c)
310 298 # permissions for can create group based on parent_id are checked
311 299 # here in the Form
312 300 available_groups = map(lambda k: safe_unicode(k[0]), c.repo_groups)
313 301 repo_group_form = RepoGroupForm(
314 302 self.request.translate, available_groups=available_groups,
315 303 can_create_in_root=can_create)()
316 304
317 305 repo_group_name = self.request.POST.get('group_name')
318 306 try:
319 307 owner = self._rhodecode_user
320 308 form_result = repo_group_form.to_python(dict(self.request.POST))
321 309 copy_permissions = form_result.get('group_copy_permissions')
322 310 repo_group = RepoGroupModel().create(
323 311 group_name=form_result['group_name_full'],
324 312 group_description=form_result['group_description'],
325 313 owner=owner.user_id,
326 314 copy_permissions=form_result['group_copy_permissions']
327 315 )
328 316 Session().flush()
329 317
330 318 repo_group_data = repo_group.get_api_data()
331 319 audit_logger.store_web(
332 320 'repo_group.create', action_data={'data': repo_group_data},
333 321 user=self._rhodecode_user)
334 322
335 323 Session().commit()
336 324
337 325 _new_group_name = form_result['group_name_full']
338 326
339 327 repo_group_url = h.link_to(
340 328 _new_group_name,
341 329 h.route_path('repo_group_home', repo_group_name=_new_group_name))
342 330 h.flash(h.literal(_('Created repository group %s')
343 331 % repo_group_url), category='success')
344 332
345 333 except formencode.Invalid as errors:
346 334 data = render(
347 335 'rhodecode:templates/admin/repo_groups/repo_group_add.mako',
348 336 self._get_template_context(c), self.request)
349 337 html = formencode.htmlfill.render(
350 338 data,
351 339 defaults=errors.value,
352 340 errors=errors.error_dict or {},
353 341 prefix_error=False,
354 342 encoding="UTF-8",
355 343 force_defaults=False
356 344 )
357 345 return Response(html)
358 346 except Exception:
359 347 log.exception("Exception during creation of repository group")
360 348 h.flash(_('Error occurred during creation of repository group %s')
361 349 % repo_group_name, category='error')
362 350 raise HTTPFound(h.route_path('home'))
363 351
364 352 affected_user_ids = [self._rhodecode_user.user_id]
365 353 if copy_permissions:
366 354 user_group_perms = repo_group.permissions(expand_from_user_groups=True)
367 355 copy_perms = [perm['user_id'] for perm in user_group_perms]
368 356 # also include those newly created by copy
369 357 affected_user_ids.extend(copy_perms)
370 358 PermissionModel().trigger_permission_flush(affected_user_ids)
371 359
372 360 raise HTTPFound(
373 361 h.route_path('repo_group_home',
374 362 repo_group_name=form_result['group_name_full']))
@@ -1,266 +1,253 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2020 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 from pyramid.view import view_config
26
27 27 from pyramid.renderers import render
28 28 from pyramid.response import Response
29 29
30 30 from rhodecode import events
31 31 from rhodecode.apps._base import BaseAppView, DataGridAppView
32 32 from rhodecode.lib.celerylib.utils import get_task_id
33 33
34 34 from rhodecode.lib.auth import (
35 35 LoginRequired, CSRFRequired, NotAnonymous,
36 36 HasPermissionAny, HasRepoGroupPermissionAny)
37 37 from rhodecode.lib import helpers as h
38 38 from rhodecode.lib.utils import repo_name_slug
39 39 from rhodecode.lib.utils2 import safe_int, safe_unicode
40 40 from rhodecode.model.forms import RepoForm
41 41 from rhodecode.model.permission import PermissionModel
42 42 from rhodecode.model.repo import RepoModel
43 43 from rhodecode.model.scm import RepoList, RepoGroupList, ScmModel
44 44 from rhodecode.model.settings import SettingsModel
45 45 from rhodecode.model.db import (
46 46 in_filter_generator, or_, func, Session, Repository, RepoGroup, User)
47 47
48 48 log = logging.getLogger(__name__)
49 49
50 50
51 51 class AdminReposView(BaseAppView, DataGridAppView):
52 52
53 53 def load_default_context(self):
54 54 c = self._get_local_tmpl_context()
55
56 55 return c
57 56
58 57 def _load_form_data(self, c):
59 58 acl_groups = RepoGroupList(RepoGroup.query().all(),
60 59 perm_set=['group.write', 'group.admin'])
61 60 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
62 61 c.repo_groups_choices = map(lambda k: safe_unicode(k[0]), c.repo_groups)
63 62 c.personal_repo_group = self._rhodecode_user.personal_repo_group
64 63
65 64 @LoginRequired()
66 65 @NotAnonymous()
67 66 # perms check inside
68 @view_config(
69 route_name='repos', request_method='GET',
70 renderer='rhodecode:templates/admin/repos/repos.mako')
71 67 def repository_list(self):
72 68 c = self.load_default_context()
73 69 return self._get_template_context(c)
74 70
75 71 @LoginRequired()
76 72 @NotAnonymous()
77 73 # perms check inside
78 @view_config(
79 route_name='repos_data', request_method='GET',
80 renderer='json_ext', xhr=True)
81 74 def repository_list_data(self):
82 75 self.load_default_context()
83 76 column_map = {
84 77 'name': 'repo_name',
85 78 'desc': 'description',
86 79 'last_change': 'updated_on',
87 80 'owner': 'user_username',
88 81 }
89 82 draw, start, limit = self._extract_chunk(self.request)
90 83 search_q, order_by, order_dir = self._extract_ordering(
91 84 self.request, column_map=column_map)
92 85
93 86 _perms = ['repository.admin']
94 87 allowed_ids = [-1] + self._rhodecode_user.repo_acl_ids_from_stack(_perms)
95 88
96 89 repos_data_total_count = Repository.query() \
97 90 .filter(or_(
98 91 # generate multiple IN to fix limitation problems
99 92 *in_filter_generator(Repository.repo_id, allowed_ids))
100 93 ) \
101 94 .count()
102 95
103 96 base_q = Session.query(
104 97 Repository.repo_id,
105 98 Repository.repo_name,
106 99 Repository.description,
107 100 Repository.repo_type,
108 101 Repository.repo_state,
109 102 Repository.private,
110 103 Repository.archived,
111 104 Repository.fork,
112 105 Repository.updated_on,
113 106 Repository._changeset_cache,
114 107 User,
115 108 ) \
116 109 .filter(or_(
117 110 # generate multiple IN to fix limitation problems
118 111 *in_filter_generator(Repository.repo_id, allowed_ids))
119 112 ) \
120 113 .join(User, User.user_id == Repository.user_id) \
121 114 .group_by(Repository, User)
122 115
123 116 if search_q:
124 117 like_expression = u'%{}%'.format(safe_unicode(search_q))
125 118 base_q = base_q.filter(or_(
126 119 Repository.repo_name.ilike(like_expression),
127 120 ))
128 121
129 122 repos_data_total_filtered_count = base_q.count()
130 123
131 124 sort_defined = False
132 125 if order_by == 'repo_name':
133 126 sort_col = func.lower(Repository.repo_name)
134 127 sort_defined = True
135 128 elif order_by == 'user_username':
136 129 sort_col = User.username
137 130 else:
138 131 sort_col = getattr(Repository, order_by, None)
139 132
140 133 if sort_defined or sort_col:
141 134 if order_dir == 'asc':
142 135 sort_col = sort_col.asc()
143 136 else:
144 137 sort_col = sort_col.desc()
145 138
146 139 base_q = base_q.order_by(sort_col)
147 140 base_q = base_q.offset(start).limit(limit)
148 141
149 142 repos_list = base_q.all()
150 143
151 144 repos_data = RepoModel().get_repos_as_dict(
152 145 repo_list=repos_list, admin=True, super_user_actions=True)
153 146
154 147 data = ({
155 148 'draw': draw,
156 149 'data': repos_data,
157 150 'recordsTotal': repos_data_total_count,
158 151 'recordsFiltered': repos_data_total_filtered_count,
159 152 })
160 153 return data
161 154
162 155 @LoginRequired()
163 156 @NotAnonymous()
164 157 # perms check inside
165 @view_config(
166 route_name='repo_new', request_method='GET',
167 renderer='rhodecode:templates/admin/repos/repo_add.mako')
168 158 def repository_new(self):
169 159 c = self.load_default_context()
170 160
171 161 new_repo = self.request.GET.get('repo', '')
172 162 parent_group_id = safe_int(self.request.GET.get('parent_group'))
173 163 _gr = RepoGroup.get(parent_group_id)
174 164
175 165 if not HasPermissionAny('hg.admin', 'hg.create.repository')():
176 166 # you're not super admin nor have global create permissions,
177 167 # but maybe you have at least write permission to a parent group ?
178 168
179 169 gr_name = _gr.group_name if _gr else None
180 170 # create repositories with write permission on group is set to true
181 171 create_on_write = HasPermissionAny('hg.create.write_on_repogroup.true')()
182 172 group_admin = HasRepoGroupPermissionAny('group.admin')(group_name=gr_name)
183 173 group_write = HasRepoGroupPermissionAny('group.write')(group_name=gr_name)
184 174 if not (group_admin or (group_write and create_on_write)):
185 175 raise HTTPForbidden()
186 176
187 177 self._load_form_data(c)
188 178 c.new_repo = repo_name_slug(new_repo)
189 179
190 180 # apply the defaults from defaults page
191 181 defaults = SettingsModel().get_default_repo_settings(strip_prefix=True)
192 182 # set checkbox to autochecked
193 183 defaults['repo_copy_permissions'] = True
194 184
195 185 parent_group_choice = '-1'
196 186 if not self._rhodecode_user.is_admin and self._rhodecode_user.personal_repo_group:
197 187 parent_group_choice = self._rhodecode_user.personal_repo_group
198 188
199 189 if parent_group_id and _gr:
200 190 if parent_group_id in [x[0] for x in c.repo_groups]:
201 191 parent_group_choice = safe_unicode(parent_group_id)
202 192
203 193 defaults.update({'repo_group': parent_group_choice})
204 194
205 195 data = render('rhodecode:templates/admin/repos/repo_add.mako',
206 196 self._get_template_context(c), self.request)
207 197 html = formencode.htmlfill.render(
208 198 data,
209 199 defaults=defaults,
210 200 encoding="UTF-8",
211 201 force_defaults=False
212 202 )
213 203 return Response(html)
214 204
215 205 @LoginRequired()
216 206 @NotAnonymous()
217 207 @CSRFRequired()
218 208 # perms check inside
219 @view_config(
220 route_name='repo_create', request_method='POST',
221 renderer='rhodecode:templates/admin/repos/repos.mako')
222 209 def repository_create(self):
223 210 c = self.load_default_context()
224 211
225 212 form_result = {}
226 213 self._load_form_data(c)
227 214
228 215 try:
229 216 # CanWriteToGroup validators checks permissions of this POST
230 217 form = RepoForm(
231 218 self.request.translate, repo_groups=c.repo_groups_choices)()
232 219 form_result = form.to_python(dict(self.request.POST))
233 220 copy_permissions = form_result.get('repo_copy_permissions')
234 221 # create is done sometimes async on celery, db transaction
235 222 # management is handled there.
236 223 task = RepoModel().create(form_result, self._rhodecode_user.user_id)
237 224 task_id = get_task_id(task)
238 225 except formencode.Invalid as errors:
239 226 data = render('rhodecode:templates/admin/repos/repo_add.mako',
240 227 self._get_template_context(c), self.request)
241 228 html = formencode.htmlfill.render(
242 229 data,
243 230 defaults=errors.value,
244 231 errors=errors.error_dict or {},
245 232 prefix_error=False,
246 233 encoding="UTF-8",
247 234 force_defaults=False
248 235 )
249 236 return Response(html)
250 237
251 238 except Exception as e:
252 239 msg = self._log_creation_exception(e, form_result.get('repo_name'))
253 240 h.flash(msg, category='error')
254 241 raise HTTPFound(h.route_path('home'))
255 242
256 243 repo_name = form_result.get('repo_name_full')
257 244
258 245 affected_user_ids = [self._rhodecode_user.user_id]
259 246 if copy_permissions:
260 247 # permission flush is done in repo creating
261 248 pass
262 249 PermissionModel().trigger_permission_flush(affected_user_ids)
263 250
264 251 raise HTTPFound(
265 252 h.route_path('repo_creating', repo_name=repo_name,
266 253 _query=dict(task_id=task_id)))
@@ -1,101 +1,95 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2020 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 from pyramid.view import view_config
23
24 24 from pyramid.httpexceptions import HTTPFound
25 25
26 26 from rhodecode.apps._base import BaseAppView
27 27 from rhodecode.apps._base.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 from rhodecode.lib import helpers as h
34 34
35 35
36 36 log = logging.getLogger(__name__)
37 37
38 38
39 39 class AdminSessionSettingsView(BaseAppView):
40
40 41 def load_default_context(self):
41 42 c = self._get_local_tmpl_context()
42
43
44 43 return c
45 44
46 45 @LoginRequired()
47 46 @HasPermissionAllDecorator('hg.admin')
48 @view_config(
49 route_name='admin_settings_sessions', request_method='GET',
50 renderer='rhodecode:templates/admin/settings/settings.mako')
51 47 def settings_sessions(self):
52 48 c = self.load_default_context()
53 49
54 50 c.active = 'sessions'
55 51 c.navlist = navigation_list(self.request)
56 52
57 53 c.cleanup_older_days = 60
58 54 older_than_seconds = 60 * 60 * 24 * c.cleanup_older_days
59 55
60 56 config = system_info.rhodecode_config().get_value()['value']['config']
61 57 c.session_model = user_sessions.get_session_handler(
62 58 config.get('beaker.session.type', 'memory'))(config)
63 59
64 60 c.session_conf = c.session_model.config
65 61 c.session_count = c.session_model.get_count()
66 62 c.session_expired_count = c.session_model.get_expired_count(
67 63 older_than_seconds)
68 64
69 65 return self._get_template_context(c)
70 66
71 67 @LoginRequired()
72 68 @HasPermissionAllDecorator('hg.admin')
73 69 @CSRFRequired()
74 @view_config(
75 route_name='admin_settings_sessions_cleanup', request_method='POST')
76 70 def settings_sessions_cleanup(self):
77 71 _ = self.request.translate
78 72 expire_days = safe_int(self.request.params.get('expire_days'))
79 73
80 74 if expire_days is None:
81 75 expire_days = 60
82 76
83 77 older_than_seconds = 60 * 60 * 24 * expire_days
84 78
85 79 config = system_info.rhodecode_config().get_value()['value']['config']
86 80 session_model = user_sessions.get_session_handler(
87 81 config.get('beaker.session.type', 'memory'))(config)
88 82
89 83 try:
90 84 session_model.clean_sessions(
91 85 older_than_seconds=older_than_seconds)
92 86 h.flash(_('Cleaned up old sessions'), category='success')
93 87 except user_sessions.CleanupCommand as msg:
94 88 h.flash(msg.message, category='warning')
95 89 except Exception as e:
96 90 log.exception('Failed session cleanup')
97 91 h.flash(_('Failed to cleanup up old sessions'), category='error')
98 92
99 93 redirect_to = self.request.resource_path(
100 94 self.context, route_name='admin_settings_sessions')
101 95 return HTTPFound(redirect_to)
@@ -1,792 +1,719 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 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 from pyramid.view import view_config
30
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._base.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.permission import PermissionModel
51 51 from rhodecode.model.repo_group import RepoGroupModel
52 52
53 53 from rhodecode.model.scm import ScmModel
54 54 from rhodecode.model.notification import EmailNotificationModel
55 55 from rhodecode.model.meta import Session
56 56 from rhodecode.model.settings import (
57 57 IssueTrackerSettingsModel, VcsSettingsModel, SettingNotFound,
58 58 SettingsModel)
59 59
60 60
61 61 log = logging.getLogger(__name__)
62 62
63 63
64 64 class AdminSettingsView(BaseAppView):
65 65
66 66 def load_default_context(self):
67 67 c = self._get_local_tmpl_context()
68 68 c.labs_active = str2bool(
69 69 rhodecode.CONFIG.get('labs_settings_active', 'true'))
70 70 c.navlist = navigation_list(self.request)
71
72 71 return c
73 72
74 73 @classmethod
75 74 def _get_ui_settings(cls):
76 75 ret = RhodeCodeUi.query().all()
77 76
78 77 if not ret:
79 78 raise Exception('Could not get application ui settings !')
80 79 settings = {}
81 80 for each in ret:
82 81 k = each.ui_key
83 82 v = each.ui_value
84 83 if k == '/':
85 84 k = 'root_path'
86 85
87 86 if k in ['push_ssl', 'publish', 'enabled']:
88 87 v = str2bool(v)
89 88
90 89 if k.find('.') != -1:
91 90 k = k.replace('.', '_')
92 91
93 92 if each.ui_section in ['hooks', 'extensions']:
94 93 v = each.ui_active
95 94
96 95 settings[each.ui_section + '_' + k] = v
97 96 return settings
98 97
99 98 @classmethod
100 99 def _form_defaults(cls):
101 100 defaults = SettingsModel().get_all_settings()
102 101 defaults.update(cls._get_ui_settings())
103 102
104 103 defaults.update({
105 104 'new_svn_branch': '',
106 105 'new_svn_tag': '',
107 106 })
108 107 return defaults
109 108
110 109 @LoginRequired()
111 110 @HasPermissionAllDecorator('hg.admin')
112 @view_config(
113 route_name='admin_settings_vcs', request_method='GET',
114 renderer='rhodecode:templates/admin/settings/settings.mako')
115 111 def settings_vcs(self):
116 112 c = self.load_default_context()
117 113 c.active = 'vcs'
118 114 model = VcsSettingsModel()
119 115 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
120 116 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
121 117
122 118 settings = self.request.registry.settings
123 119 c.svn_proxy_generate_config = settings[generate_config]
124 120
125 121 defaults = self._form_defaults()
126 122
127 123 model.create_largeobjects_dirs_if_needed(defaults['paths_root_path'])
128 124
129 125 data = render('rhodecode:templates/admin/settings/settings.mako',
130 126 self._get_template_context(c), self.request)
131 127 html = formencode.htmlfill.render(
132 128 data,
133 129 defaults=defaults,
134 130 encoding="UTF-8",
135 131 force_defaults=False
136 132 )
137 133 return Response(html)
138 134
139 135 @LoginRequired()
140 136 @HasPermissionAllDecorator('hg.admin')
141 137 @CSRFRequired()
142 @view_config(
143 route_name='admin_settings_vcs_update', request_method='POST',
144 renderer='rhodecode:templates/admin/settings/settings.mako')
145 138 def settings_vcs_update(self):
146 139 _ = self.request.translate
147 140 c = self.load_default_context()
148 141 c.active = 'vcs'
149 142
150 143 model = VcsSettingsModel()
151 144 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
152 145 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
153 146
154 147 settings = self.request.registry.settings
155 148 c.svn_proxy_generate_config = settings[generate_config]
156 149
157 150 application_form = ApplicationUiSettingsForm(self.request.translate)()
158 151
159 152 try:
160 153 form_result = application_form.to_python(dict(self.request.POST))
161 154 except formencode.Invalid as errors:
162 155 h.flash(
163 156 _("Some form inputs contain invalid data."),
164 157 category='error')
165 158 data = render('rhodecode:templates/admin/settings/settings.mako',
166 159 self._get_template_context(c), self.request)
167 160 html = formencode.htmlfill.render(
168 161 data,
169 162 defaults=errors.value,
170 163 errors=errors.error_dict or {},
171 164 prefix_error=False,
172 165 encoding="UTF-8",
173 166 force_defaults=False
174 167 )
175 168 return Response(html)
176 169
177 170 try:
178 171 if c.visual.allow_repo_location_change:
179 172 model.update_global_path_setting(form_result['paths_root_path'])
180 173
181 174 model.update_global_ssl_setting(form_result['web_push_ssl'])
182 175 model.update_global_hook_settings(form_result)
183 176
184 177 model.create_or_update_global_svn_settings(form_result)
185 178 model.create_or_update_global_hg_settings(form_result)
186 179 model.create_or_update_global_git_settings(form_result)
187 180 model.create_or_update_global_pr_settings(form_result)
188 181 except Exception:
189 182 log.exception("Exception while updating settings")
190 183 h.flash(_('Error occurred during updating '
191 184 'application settings'), category='error')
192 185 else:
193 186 Session().commit()
194 187 h.flash(_('Updated VCS settings'), category='success')
195 188 raise HTTPFound(h.route_path('admin_settings_vcs'))
196 189
197 190 data = render('rhodecode:templates/admin/settings/settings.mako',
198 191 self._get_template_context(c), self.request)
199 192 html = formencode.htmlfill.render(
200 193 data,
201 194 defaults=self._form_defaults(),
202 195 encoding="UTF-8",
203 196 force_defaults=False
204 197 )
205 198 return Response(html)
206 199
207 200 @LoginRequired()
208 201 @HasPermissionAllDecorator('hg.admin')
209 202 @CSRFRequired()
210 @view_config(
211 route_name='admin_settings_vcs_svn_pattern_delete', request_method='POST',
212 renderer='json_ext', xhr=True)
213 203 def settings_vcs_delete_svn_pattern(self):
214 204 delete_pattern_id = self.request.POST.get('delete_svn_pattern')
215 205 model = VcsSettingsModel()
216 206 try:
217 207 model.delete_global_svn_pattern(delete_pattern_id)
218 208 except SettingNotFound:
219 209 log.exception(
220 210 'Failed to delete svn_pattern with id %s', delete_pattern_id)
221 211 raise HTTPNotFound()
222 212
223 213 Session().commit()
224 214 return True
225 215
226 216 @LoginRequired()
227 217 @HasPermissionAllDecorator('hg.admin')
228 @view_config(
229 route_name='admin_settings_mapping', request_method='GET',
230 renderer='rhodecode:templates/admin/settings/settings.mako')
231 218 def settings_mapping(self):
232 219 c = self.load_default_context()
233 220 c.active = 'mapping'
234 221
235 222 data = render('rhodecode:templates/admin/settings/settings.mako',
236 223 self._get_template_context(c), self.request)
237 224 html = formencode.htmlfill.render(
238 225 data,
239 226 defaults=self._form_defaults(),
240 227 encoding="UTF-8",
241 228 force_defaults=False
242 229 )
243 230 return Response(html)
244 231
245 232 @LoginRequired()
246 233 @HasPermissionAllDecorator('hg.admin')
247 234 @CSRFRequired()
248 @view_config(
249 route_name='admin_settings_mapping_update', request_method='POST',
250 renderer='rhodecode:templates/admin/settings/settings.mako')
251 235 def settings_mapping_update(self):
252 236 _ = self.request.translate
253 237 c = self.load_default_context()
254 238 c.active = 'mapping'
255 239 rm_obsolete = self.request.POST.get('destroy', False)
256 240 invalidate_cache = self.request.POST.get('invalidate', False)
257 241 log.debug('rescanning repo location with destroy obsolete=%s', rm_obsolete)
258 242
259 243 if invalidate_cache:
260 244 log.debug('invalidating all repositories cache')
261 245 for repo in Repository.get_all():
262 246 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
263 247
264 248 filesystem_repos = ScmModel().repo_scan()
265 249 added, removed = repo2db_mapper(filesystem_repos, rm_obsolete)
266 250 PermissionModel().trigger_permission_flush()
267 251
268 252 _repr = lambda l: ', '.join(map(safe_unicode, l)) or '-'
269 253 h.flash(_('Repositories successfully '
270 254 'rescanned added: %s ; removed: %s') %
271 255 (_repr(added), _repr(removed)),
272 256 category='success')
273 257 raise HTTPFound(h.route_path('admin_settings_mapping'))
274 258
275 259 @LoginRequired()
276 260 @HasPermissionAllDecorator('hg.admin')
277 @view_config(
278 route_name='admin_settings', request_method='GET',
279 renderer='rhodecode:templates/admin/settings/settings.mako')
280 @view_config(
281 route_name='admin_settings_global', request_method='GET',
282 renderer='rhodecode:templates/admin/settings/settings.mako')
283 261 def settings_global(self):
284 262 c = self.load_default_context()
285 263 c.active = 'global'
286 264 c.personal_repo_group_default_pattern = RepoGroupModel()\
287 265 .get_personal_group_name_pattern()
288 266
289 267 data = render('rhodecode:templates/admin/settings/settings.mako',
290 268 self._get_template_context(c), self.request)
291 269 html = formencode.htmlfill.render(
292 270 data,
293 271 defaults=self._form_defaults(),
294 272 encoding="UTF-8",
295 273 force_defaults=False
296 274 )
297 275 return Response(html)
298 276
299 277 @LoginRequired()
300 278 @HasPermissionAllDecorator('hg.admin')
301 279 @CSRFRequired()
302 @view_config(
303 route_name='admin_settings_update', request_method='POST',
304 renderer='rhodecode:templates/admin/settings/settings.mako')
305 @view_config(
306 route_name='admin_settings_global_update', request_method='POST',
307 renderer='rhodecode:templates/admin/settings/settings.mako')
308 280 def settings_global_update(self):
309 281 _ = self.request.translate
310 282 c = self.load_default_context()
311 283 c.active = 'global'
312 284 c.personal_repo_group_default_pattern = RepoGroupModel()\
313 285 .get_personal_group_name_pattern()
314 286 application_form = ApplicationSettingsForm(self.request.translate)()
315 287 try:
316 288 form_result = application_form.to_python(dict(self.request.POST))
317 289 except formencode.Invalid as errors:
318 290 h.flash(
319 291 _("Some form inputs contain invalid data."),
320 292 category='error')
321 293 data = render('rhodecode:templates/admin/settings/settings.mako',
322 294 self._get_template_context(c), self.request)
323 295 html = formencode.htmlfill.render(
324 296 data,
325 297 defaults=errors.value,
326 298 errors=errors.error_dict or {},
327 299 prefix_error=False,
328 300 encoding="UTF-8",
329 301 force_defaults=False
330 302 )
331 303 return Response(html)
332 304
333 305 settings = [
334 306 ('title', 'rhodecode_title', 'unicode'),
335 307 ('realm', 'rhodecode_realm', 'unicode'),
336 308 ('pre_code', 'rhodecode_pre_code', 'unicode'),
337 309 ('post_code', 'rhodecode_post_code', 'unicode'),
338 310 ('captcha_public_key', 'rhodecode_captcha_public_key', 'unicode'),
339 311 ('captcha_private_key', 'rhodecode_captcha_private_key', 'unicode'),
340 312 ('create_personal_repo_group', 'rhodecode_create_personal_repo_group', 'bool'),
341 313 ('personal_repo_group_pattern', 'rhodecode_personal_repo_group_pattern', 'unicode'),
342 314 ]
343 315 try:
344 316 for setting, form_key, type_ in settings:
345 317 sett = SettingsModel().create_or_update_setting(
346 318 setting, form_result[form_key], type_)
347 319 Session().add(sett)
348 320
349 321 Session().commit()
350 322 SettingsModel().invalidate_settings_cache()
351 323 h.flash(_('Updated application settings'), category='success')
352 324 except Exception:
353 325 log.exception("Exception while updating application settings")
354 326 h.flash(
355 327 _('Error occurred during updating application settings'),
356 328 category='error')
357 329
358 330 raise HTTPFound(h.route_path('admin_settings_global'))
359 331
360 332 @LoginRequired()
361 333 @HasPermissionAllDecorator('hg.admin')
362 @view_config(
363 route_name='admin_settings_visual', request_method='GET',
364 renderer='rhodecode:templates/admin/settings/settings.mako')
365 334 def settings_visual(self):
366 335 c = self.load_default_context()
367 336 c.active = 'visual'
368 337
369 338 data = render('rhodecode:templates/admin/settings/settings.mako',
370 339 self._get_template_context(c), self.request)
371 340 html = formencode.htmlfill.render(
372 341 data,
373 342 defaults=self._form_defaults(),
374 343 encoding="UTF-8",
375 344 force_defaults=False
376 345 )
377 346 return Response(html)
378 347
379 348 @LoginRequired()
380 349 @HasPermissionAllDecorator('hg.admin')
381 350 @CSRFRequired()
382 @view_config(
383 route_name='admin_settings_visual_update', request_method='POST',
384 renderer='rhodecode:templates/admin/settings/settings.mako')
385 351 def settings_visual_update(self):
386 352 _ = self.request.translate
387 353 c = self.load_default_context()
388 354 c.active = 'visual'
389 355 application_form = ApplicationVisualisationForm(self.request.translate)()
390 356 try:
391 357 form_result = application_form.to_python(dict(self.request.POST))
392 358 except formencode.Invalid as errors:
393 359 h.flash(
394 360 _("Some form inputs contain invalid data."),
395 361 category='error')
396 362 data = render('rhodecode:templates/admin/settings/settings.mako',
397 363 self._get_template_context(c), self.request)
398 364 html = formencode.htmlfill.render(
399 365 data,
400 366 defaults=errors.value,
401 367 errors=errors.error_dict or {},
402 368 prefix_error=False,
403 369 encoding="UTF-8",
404 370 force_defaults=False
405 371 )
406 372 return Response(html)
407 373
408 374 try:
409 375 settings = [
410 376 ('show_public_icon', 'rhodecode_show_public_icon', 'bool'),
411 377 ('show_private_icon', 'rhodecode_show_private_icon', 'bool'),
412 378 ('stylify_metatags', 'rhodecode_stylify_metatags', 'bool'),
413 379 ('repository_fields', 'rhodecode_repository_fields', 'bool'),
414 380 ('dashboard_items', 'rhodecode_dashboard_items', 'int'),
415 381 ('admin_grid_items', 'rhodecode_admin_grid_items', 'int'),
416 382 ('show_version', 'rhodecode_show_version', 'bool'),
417 383 ('use_gravatar', 'rhodecode_use_gravatar', 'bool'),
418 384 ('markup_renderer', 'rhodecode_markup_renderer', 'unicode'),
419 385 ('gravatar_url', 'rhodecode_gravatar_url', 'unicode'),
420 386 ('clone_uri_tmpl', 'rhodecode_clone_uri_tmpl', 'unicode'),
421 387 ('clone_uri_ssh_tmpl', 'rhodecode_clone_uri_ssh_tmpl', 'unicode'),
422 388 ('support_url', 'rhodecode_support_url', 'unicode'),
423 389 ('show_revision_number', 'rhodecode_show_revision_number', 'bool'),
424 390 ('show_sha_length', 'rhodecode_show_sha_length', 'int'),
425 391 ]
426 392 for setting, form_key, type_ in settings:
427 393 sett = SettingsModel().create_or_update_setting(
428 394 setting, form_result[form_key], type_)
429 395 Session().add(sett)
430 396
431 397 Session().commit()
432 398 SettingsModel().invalidate_settings_cache()
433 399 h.flash(_('Updated visualisation settings'), category='success')
434 400 except Exception:
435 401 log.exception("Exception updating visualization settings")
436 402 h.flash(_('Error occurred during updating '
437 403 'visualisation settings'),
438 404 category='error')
439 405
440 406 raise HTTPFound(h.route_path('admin_settings_visual'))
441 407
442 408 @LoginRequired()
443 409 @HasPermissionAllDecorator('hg.admin')
444 @view_config(
445 route_name='admin_settings_issuetracker', request_method='GET',
446 renderer='rhodecode:templates/admin/settings/settings.mako')
447 410 def settings_issuetracker(self):
448 411 c = self.load_default_context()
449 412 c.active = 'issuetracker'
450 413 defaults = c.rc_config
451 414
452 415 entry_key = 'rhodecode_issuetracker_pat_'
453 416
454 417 c.issuetracker_entries = {}
455 418 for k, v in defaults.items():
456 419 if k.startswith(entry_key):
457 420 uid = k[len(entry_key):]
458 421 c.issuetracker_entries[uid] = None
459 422
460 423 for uid in c.issuetracker_entries:
461 424 c.issuetracker_entries[uid] = AttributeDict({
462 425 'pat': defaults.get('rhodecode_issuetracker_pat_' + uid),
463 426 'url': defaults.get('rhodecode_issuetracker_url_' + uid),
464 427 'pref': defaults.get('rhodecode_issuetracker_pref_' + uid),
465 428 'desc': defaults.get('rhodecode_issuetracker_desc_' + uid),
466 429 })
467 430
468 431 return self._get_template_context(c)
469 432
470 433 @LoginRequired()
471 434 @HasPermissionAllDecorator('hg.admin')
472 435 @CSRFRequired()
473 @view_config(
474 route_name='admin_settings_issuetracker_test', request_method='POST',
475 renderer='string', xhr=True)
476 436 def settings_issuetracker_test(self):
477 437 error_container = []
478 438
479 439 urlified_commit = h.urlify_commit_message(
480 440 self.request.POST.get('test_text', ''),
481 441 'repo_group/test_repo1', error_container=error_container)
482 442 if error_container:
483 443 def converter(inp):
484 444 return h.html_escape(unicode(inp))
485 445
486 446 return 'ERRORS: ' + '\n'.join(map(converter, error_container))
487 447
488 448 return urlified_commit
489 449
490 450 @LoginRequired()
491 451 @HasPermissionAllDecorator('hg.admin')
492 452 @CSRFRequired()
493 @view_config(
494 route_name='admin_settings_issuetracker_update', request_method='POST',
495 renderer='rhodecode:templates/admin/settings/settings.mako')
496 453 def settings_issuetracker_update(self):
497 454 _ = self.request.translate
498 455 self.load_default_context()
499 456 settings_model = IssueTrackerSettingsModel()
500 457
501 458 try:
502 459 form = IssueTrackerPatternsForm(self.request.translate)()
503 460 data = form.to_python(self.request.POST)
504 461 except formencode.Invalid as errors:
505 462 log.exception('Failed to add new pattern')
506 463 error = errors
507 464 h.flash(_('Invalid issue tracker pattern: {}'.format(error)),
508 465 category='error')
509 466 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
510 467
511 468 if data:
512 469 for uid in data.get('delete_patterns', []):
513 470 settings_model.delete_entries(uid)
514 471
515 472 for pattern in data.get('patterns', []):
516 473 for setting, value, type_ in pattern:
517 474 sett = settings_model.create_or_update_setting(
518 475 setting, value, type_)
519 476 Session().add(sett)
520 477
521 478 Session().commit()
522 479
523 480 SettingsModel().invalidate_settings_cache()
524 481 h.flash(_('Updated issue tracker entries'), category='success')
525 482 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
526 483
527 484 @LoginRequired()
528 485 @HasPermissionAllDecorator('hg.admin')
529 486 @CSRFRequired()
530 @view_config(
531 route_name='admin_settings_issuetracker_delete', request_method='POST',
532 renderer='json_ext', xhr=True)
533 487 def settings_issuetracker_delete(self):
534 488 _ = self.request.translate
535 489 self.load_default_context()
536 490 uid = self.request.POST.get('uid')
537 491 try:
538 492 IssueTrackerSettingsModel().delete_entries(uid)
539 493 except Exception:
540 494 log.exception('Failed to delete issue tracker setting %s', uid)
541 495 raise HTTPNotFound()
542 496
543 497 SettingsModel().invalidate_settings_cache()
544 498 h.flash(_('Removed issue tracker entry.'), category='success')
545 499
546 500 return {'deleted': uid}
547 501
548 502 @LoginRequired()
549 503 @HasPermissionAllDecorator('hg.admin')
550 @view_config(
551 route_name='admin_settings_email', request_method='GET',
552 renderer='rhodecode:templates/admin/settings/settings.mako')
553 504 def settings_email(self):
554 505 c = self.load_default_context()
555 506 c.active = 'email'
556 507 c.rhodecode_ini = rhodecode.CONFIG
557 508
558 509 data = render('rhodecode:templates/admin/settings/settings.mako',
559 510 self._get_template_context(c), self.request)
560 511 html = formencode.htmlfill.render(
561 512 data,
562 513 defaults=self._form_defaults(),
563 514 encoding="UTF-8",
564 515 force_defaults=False
565 516 )
566 517 return Response(html)
567 518
568 519 @LoginRequired()
569 520 @HasPermissionAllDecorator('hg.admin')
570 521 @CSRFRequired()
571 @view_config(
572 route_name='admin_settings_email_update', request_method='POST',
573 renderer='rhodecode:templates/admin/settings/settings.mako')
574 522 def settings_email_update(self):
575 523 _ = self.request.translate
576 524 c = self.load_default_context()
577 525 c.active = 'email'
578 526
579 527 test_email = self.request.POST.get('test_email')
580 528
581 529 if not test_email:
582 530 h.flash(_('Please enter email address'), category='error')
583 531 raise HTTPFound(h.route_path('admin_settings_email'))
584 532
585 533 email_kwargs = {
586 534 'date': datetime.datetime.now(),
587 535 'user': self._rhodecode_db_user
588 536 }
589 537
590 538 (subject, email_body, email_body_plaintext) = EmailNotificationModel().render_email(
591 539 EmailNotificationModel.TYPE_EMAIL_TEST, **email_kwargs)
592 540
593 541 recipients = [test_email] if test_email else None
594 542
595 543 run_task(tasks.send_email, recipients, subject,
596 544 email_body_plaintext, email_body)
597 545
598 546 h.flash(_('Send email task created'), category='success')
599 547 raise HTTPFound(h.route_path('admin_settings_email'))
600 548
601 549 @LoginRequired()
602 550 @HasPermissionAllDecorator('hg.admin')
603 @view_config(
604 route_name='admin_settings_hooks', request_method='GET',
605 renderer='rhodecode:templates/admin/settings/settings.mako')
606 551 def settings_hooks(self):
607 552 c = self.load_default_context()
608 553 c.active = 'hooks'
609 554
610 555 model = SettingsModel()
611 556 c.hooks = model.get_builtin_hooks()
612 557 c.custom_hooks = model.get_custom_hooks()
613 558
614 559 data = render('rhodecode:templates/admin/settings/settings.mako',
615 560 self._get_template_context(c), self.request)
616 561 html = formencode.htmlfill.render(
617 562 data,
618 563 defaults=self._form_defaults(),
619 564 encoding="UTF-8",
620 565 force_defaults=False
621 566 )
622 567 return Response(html)
623 568
624 569 @LoginRequired()
625 570 @HasPermissionAllDecorator('hg.admin')
626 571 @CSRFRequired()
627 @view_config(
628 route_name='admin_settings_hooks_update', request_method='POST',
629 renderer='rhodecode:templates/admin/settings/settings.mako')
630 @view_config(
631 route_name='admin_settings_hooks_delete', request_method='POST',
632 renderer='rhodecode:templates/admin/settings/settings.mako')
633 572 def settings_hooks_update(self):
634 573 _ = self.request.translate
635 574 c = self.load_default_context()
636 575 c.active = 'hooks'
637 576 if c.visual.allow_custom_hooks_settings:
638 577 ui_key = self.request.POST.get('new_hook_ui_key')
639 578 ui_value = self.request.POST.get('new_hook_ui_value')
640 579
641 580 hook_id = self.request.POST.get('hook_id')
642 581 new_hook = False
643 582
644 583 model = SettingsModel()
645 584 try:
646 585 if ui_value and ui_key:
647 586 model.create_or_update_hook(ui_key, ui_value)
648 587 h.flash(_('Added new hook'), category='success')
649 588 new_hook = True
650 589 elif hook_id:
651 590 RhodeCodeUi.delete(hook_id)
652 591 Session().commit()
653 592
654 593 # check for edits
655 594 update = False
656 595 _d = self.request.POST.dict_of_lists()
657 596 for k, v in zip(_d.get('hook_ui_key', []),
658 597 _d.get('hook_ui_value_new', [])):
659 598 model.create_or_update_hook(k, v)
660 599 update = True
661 600
662 601 if update and not new_hook:
663 602 h.flash(_('Updated hooks'), category='success')
664 603 Session().commit()
665 604 except Exception:
666 605 log.exception("Exception during hook creation")
667 606 h.flash(_('Error occurred during hook creation'),
668 607 category='error')
669 608
670 609 raise HTTPFound(h.route_path('admin_settings_hooks'))
671 610
672 611 @LoginRequired()
673 612 @HasPermissionAllDecorator('hg.admin')
674 @view_config(
675 route_name='admin_settings_search', request_method='GET',
676 renderer='rhodecode:templates/admin/settings/settings.mako')
677 613 def settings_search(self):
678 614 c = self.load_default_context()
679 615 c.active = 'search'
680 616
681 617 c.searcher = searcher_from_config(self.request.registry.settings)
682 618 c.statistics = c.searcher.statistics(self.request.translate)
683 619
684 620 return self._get_template_context(c)
685 621
686 622 @LoginRequired()
687 623 @HasPermissionAllDecorator('hg.admin')
688 @view_config(
689 route_name='admin_settings_automation', request_method='GET',
690 renderer='rhodecode:templates/admin/settings/settings.mako')
691 624 def settings_automation(self):
692 625 c = self.load_default_context()
693 626 c.active = 'automation'
694 627
695 628 return self._get_template_context(c)
696 629
697 630 @LoginRequired()
698 631 @HasPermissionAllDecorator('hg.admin')
699 @view_config(
700 route_name='admin_settings_labs', request_method='GET',
701 renderer='rhodecode:templates/admin/settings/settings.mako')
702 632 def settings_labs(self):
703 633 c = self.load_default_context()
704 634 if not c.labs_active:
705 635 raise HTTPFound(h.route_path('admin_settings'))
706 636
707 637 c.active = 'labs'
708 638 c.lab_settings = _LAB_SETTINGS
709 639
710 640 data = render('rhodecode:templates/admin/settings/settings.mako',
711 641 self._get_template_context(c), self.request)
712 642 html = formencode.htmlfill.render(
713 643 data,
714 644 defaults=self._form_defaults(),
715 645 encoding="UTF-8",
716 646 force_defaults=False
717 647 )
718 648 return Response(html)
719 649
720 650 @LoginRequired()
721 651 @HasPermissionAllDecorator('hg.admin')
722 652 @CSRFRequired()
723 @view_config(
724 route_name='admin_settings_labs_update', request_method='POST',
725 renderer='rhodecode:templates/admin/settings/settings.mako')
726 653 def settings_labs_update(self):
727 654 _ = self.request.translate
728 655 c = self.load_default_context()
729 656 c.active = 'labs'
730 657
731 658 application_form = LabsSettingsForm(self.request.translate)()
732 659 try:
733 660 form_result = application_form.to_python(dict(self.request.POST))
734 661 except formencode.Invalid as errors:
735 662 h.flash(
736 663 _("Some form inputs contain invalid data."),
737 664 category='error')
738 665 data = render('rhodecode:templates/admin/settings/settings.mako',
739 666 self._get_template_context(c), self.request)
740 667 html = formencode.htmlfill.render(
741 668 data,
742 669 defaults=errors.value,
743 670 errors=errors.error_dict or {},
744 671 prefix_error=False,
745 672 encoding="UTF-8",
746 673 force_defaults=False
747 674 )
748 675 return Response(html)
749 676
750 677 try:
751 678 session = Session()
752 679 for setting in _LAB_SETTINGS:
753 680 setting_name = setting.key[len('rhodecode_'):]
754 681 sett = SettingsModel().create_or_update_setting(
755 682 setting_name, form_result[setting.key], setting.type)
756 683 session.add(sett)
757 684
758 685 except Exception:
759 686 log.exception('Exception while updating lab settings')
760 687 h.flash(_('Error occurred during updating labs settings'),
761 688 category='error')
762 689 else:
763 690 Session().commit()
764 691 SettingsModel().invalidate_settings_cache()
765 692 h.flash(_('Updated Labs settings'), category='success')
766 693 raise HTTPFound(h.route_path('admin_settings_labs'))
767 694
768 695 data = render('rhodecode:templates/admin/settings/settings.mako',
769 696 self._get_template_context(c), self.request)
770 697 html = formencode.htmlfill.render(
771 698 data,
772 699 defaults=self._form_defaults(),
773 700 encoding="UTF-8",
774 701 force_defaults=False
775 702 )
776 703 return Response(html)
777 704
778 705
779 706 # :param key: name of the setting including the 'rhodecode_' prefix
780 707 # :param type: the RhodeCodeSetting type to use.
781 708 # :param group: the i18ned group in which we should dispaly this setting
782 709 # :param label: the i18ned label we should display for this setting
783 710 # :param help: the i18ned help we should dispaly for this setting
784 711 LabSetting = collections.namedtuple(
785 712 'LabSetting', ('key', 'type', 'group', 'label', 'help'))
786 713
787 714
788 715 # This list has to be kept in sync with the form
789 716 # rhodecode.model.forms.LabsSettingsForm.
790 717 _LAB_SETTINGS = [
791 718
792 719 ]
@@ -1,59 +1,56 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2020 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 from pyramid.view import view_config
23
24 24
25 25 from rhodecode.apps._base import BaseAppView
26 26 from rhodecode.apps.svn_support.utils import generate_mod_dav_svn_config
27 27 from rhodecode.lib.auth import (
28 28 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
29 29
30 30 log = logging.getLogger(__name__)
31 31
32 32
33 class SvnConfigAdminSettingsView(BaseAppView):
33 class AdminSvnConfigView(BaseAppView):
34 34
35 35 @LoginRequired()
36 36 @HasPermissionAllDecorator('hg.admin')
37 37 @CSRFRequired()
38 @view_config(
39 route_name='admin_settings_vcs_svn_generate_cfg',
40 request_method='POST', renderer='json')
41 38 def vcs_svn_generate_config(self):
42 39 _ = self.request.translate
43 40 try:
44 41 file_path = generate_mod_dav_svn_config(self.request.registry)
45 42 msg = {
46 43 'message': _('Apache configuration for Subversion generated at `{}`.').format(file_path),
47 44 'level': 'success',
48 45 }
49 46 except Exception:
50 47 log.exception(
51 48 'Exception while generating the Apache '
52 49 'configuration for Subversion.')
53 50 msg = {
54 51 'message': _('Failed to generate the Apache configuration for Subversion.'),
55 52 'level': 'error',
56 53 }
57 54
58 55 data = {'message': msg}
59 56 return data
@@ -1,206 +1,200 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2020 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
24 from pyramid.view import view_config
24
25 25
26 26 import rhodecode
27 27 from rhodecode.apps._base import BaseAppView
28 28 from rhodecode.apps._base.navigation import navigation_list
29 29 from rhodecode.lib import helpers as h
30 30 from rhodecode.lib.auth import (LoginRequired, HasPermissionAllDecorator)
31 31 from rhodecode.lib.utils2 import str2bool
32 32 from rhodecode.lib import system_info
33 33 from rhodecode.model.update import UpdateModel
34 34
35 35 log = logging.getLogger(__name__)
36 36
37 37
38 38 class AdminSystemInfoSettingsView(BaseAppView):
39 39 def load_default_context(self):
40 40 c = self._get_local_tmpl_context()
41 41 return c
42 42
43 43 @LoginRequired()
44 44 @HasPermissionAllDecorator('hg.admin')
45 @view_config(
46 route_name='admin_settings_system', request_method='GET',
47 renderer='rhodecode:templates/admin/settings/settings.mako')
48 45 def settings_system_info(self):
49 46 _ = self.request.translate
50 47 c = self.load_default_context()
51 48
52 49 c.active = 'system'
53 50 c.navlist = navigation_list(self.request)
54 51
55 52 # TODO(marcink), figure out how to allow only selected users to do this
56 53 c.allowed_to_snapshot = self._rhodecode_user.admin
57 54
58 55 snapshot = str2bool(self.request.params.get('snapshot'))
59 56
60 57 c.rhodecode_update_url = UpdateModel().get_update_url()
61 58 server_info = system_info.get_system_info(self.request.environ)
62 59
63 60 for key, val in server_info.items():
64 61 setattr(c, key, val)
65 62
66 63 def val(name, subkey='human_value'):
67 64 return server_info[name][subkey]
68 65
69 66 def state(name):
70 67 return server_info[name]['state']
71 68
72 69 def val2(name):
73 70 val = server_info[name]['human_value']
74 71 state = server_info[name]['state']
75 72 return val, state
76 73
77 74 update_info_msg = _('Note: please make sure this server can '
78 75 'access `${url}` for the update link to work',
79 76 mapping=dict(url=c.rhodecode_update_url))
80 77 version = UpdateModel().get_stored_version()
81 78 is_outdated = UpdateModel().is_outdated(
82 79 rhodecode.__version__, version)
83 80 update_state = {
84 81 'type': 'warning',
85 82 'message': 'New version available: {}'.format(version)
86 83 } \
87 84 if is_outdated else {}
88 85 c.data_items = [
89 86 # update info
90 87 (_('Update info'), h.literal(
91 88 '<span class="link" id="check_for_update" >%s.</span>' % (
92 89 _('Check for updates')) +
93 90 '<br/> <span >%s.</span>' % (update_info_msg)
94 91 ), ''),
95 92
96 93 # RhodeCode specific
97 94 (_('RhodeCode Version'), val('rhodecode_app')['text'], state('rhodecode_app')),
98 95 (_('Latest version'), version, update_state),
99 96 (_('RhodeCode Base URL'), val('rhodecode_config')['config'].get('app.base_url'), state('rhodecode_config')),
100 97 (_('RhodeCode Server IP'), val('server')['server_ip'], state('server')),
101 98 (_('RhodeCode Server ID'), val('server')['server_id'], state('server')),
102 99 (_('RhodeCode Configuration'), val('rhodecode_config')['path'], state('rhodecode_config')),
103 100 (_('RhodeCode Certificate'), val('rhodecode_config')['cert_path'], state('rhodecode_config')),
104 101 (_('Workers'), val('rhodecode_config')['config']['server:main'].get('workers', '?'), state('rhodecode_config')),
105 102 (_('Worker Type'), val('rhodecode_config')['config']['server:main'].get('worker_class', 'sync'), state('rhodecode_config')),
106 103 ('', '', ''), # spacer
107 104
108 105 # Database
109 106 (_('Database'), val('database')['url'], state('database')),
110 107 (_('Database version'), val('database')['version'], state('database')),
111 108 ('', '', ''), # spacer
112 109
113 110 # Platform/Python
114 111 (_('Platform'), val('platform')['name'], state('platform')),
115 112 (_('Platform UUID'), val('platform')['uuid'], state('platform')),
116 113 (_('Lang'), val('locale'), state('locale')),
117 114 (_('Python version'), val('python')['version'], state('python')),
118 115 (_('Python path'), val('python')['executable'], state('python')),
119 116 ('', '', ''), # spacer
120 117
121 118 # Systems stats
122 119 (_('CPU'), val('cpu')['text'], state('cpu')),
123 120 (_('Load'), val('load')['text'], state('load')),
124 121 (_('Memory'), val('memory')['text'], state('memory')),
125 122 (_('Uptime'), val('uptime')['text'], state('uptime')),
126 123 ('', '', ''), # spacer
127 124
128 125 # ulimit
129 126 (_('Ulimit'), val('ulimit')['text'], state('ulimit')),
130 127
131 128 # Repo storage
132 129 (_('Storage location'), val('storage')['path'], state('storage')),
133 130 (_('Storage info'), val('storage')['text'], state('storage')),
134 131 (_('Storage inodes'), val('storage_inodes')['text'], state('storage_inodes')),
135 132
136 133 (_('Gist storage location'), val('storage_gist')['path'], state('storage_gist')),
137 134 (_('Gist storage info'), val('storage_gist')['text'], state('storage_gist')),
138 135
139 136 (_('Archive cache storage location'), val('storage_archive')['path'], state('storage_archive')),
140 137 (_('Archive cache info'), val('storage_archive')['text'], state('storage_archive')),
141 138
142 139 (_('Temp storage location'), val('storage_temp')['path'], state('storage_temp')),
143 140 (_('Temp storage info'), val('storage_temp')['text'], state('storage_temp')),
144 141
145 142 (_('Search info'), val('search')['text'], state('search')),
146 143 (_('Search location'), val('search')['location'], state('search')),
147 144 ('', '', ''), # spacer
148 145
149 146 # VCS specific
150 147 (_('VCS Backends'), val('vcs_backends'), state('vcs_backends')),
151 148 (_('VCS Server'), val('vcs_server')['text'], state('vcs_server')),
152 149 (_('GIT'), val('git'), state('git')),
153 150 (_('HG'), val('hg'), state('hg')),
154 151 (_('SVN'), val('svn'), state('svn')),
155 152
156 153 ]
157 154
158 155 c.vcsserver_data_items = [
159 156 (k, v) for k,v in (val('vcs_server_config') or {}).items()
160 157 ]
161 158
162 159 if snapshot:
163 160 if c.allowed_to_snapshot:
164 161 c.data_items.pop(0) # remove server info
165 162 self.request.override_renderer = 'admin/settings/settings_system_snapshot.mako'
166 163 else:
167 164 h.flash('You are not allowed to do this', category='warning')
168 165 return self._get_template_context(c)
169 166
170 167 @LoginRequired()
171 168 @HasPermissionAllDecorator('hg.admin')
172 @view_config(
173 route_name='admin_settings_system_update', request_method='GET',
174 renderer='rhodecode:templates/admin/settings/settings_system_update.mako')
175 169 def settings_system_info_check_update(self):
176 170 _ = self.request.translate
177 171 c = self.load_default_context()
178 172
179 173 update_url = UpdateModel().get_update_url()
180 174
181 175 _err = lambda s: '<div style="color:#ff8888; padding:4px 0px">{}</div>'.format(s)
182 176 try:
183 177 data = UpdateModel().get_update_data(update_url)
184 178 except urllib2.URLError as e:
185 179 log.exception("Exception contacting upgrade server")
186 180 self.request.override_renderer = 'string'
187 181 return _err('Failed to contact upgrade server: %r' % e)
188 182 except ValueError as e:
189 183 log.exception("Bad data sent from update server")
190 184 self.request.override_renderer = 'string'
191 185 return _err('Bad data sent from update server')
192 186
193 187 latest = data['versions'][0]
194 188
195 189 c.update_url = update_url
196 190 c.latest_data = latest
197 191 c.latest_ver = latest['version']
198 192 c.cur_ver = rhodecode.__version__
199 193 c.should_upgrade = False
200 194
201 195 is_oudated = UpdateModel().is_outdated(c.cur_ver, c.latest_ver)
202 196 if is_oudated:
203 197 c.should_upgrade = True
204 198 c.important_notices = latest['general']
205 199 UpdateModel().store_version(latest['version'])
206 200 return self._get_template_context(c)
@@ -1,268 +1,254 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2020 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 from pyramid.view import view_config
27
28 28 from pyramid.response import Response
29 29 from pyramid.renderers import render
30 30
31 31 from rhodecode import events
32 32 from rhodecode.apps._base import BaseAppView, DataGridAppView
33 33 from rhodecode.lib.auth import (
34 34 LoginRequired, NotAnonymous, CSRFRequired, HasPermissionAnyDecorator)
35 35 from rhodecode.lib import helpers as h, audit_logger
36 36 from rhodecode.lib.utils2 import safe_unicode
37 37
38 38 from rhodecode.model.forms import UserGroupForm
39 39 from rhodecode.model.permission import PermissionModel
40 40 from rhodecode.model.scm import UserGroupList
41 41 from rhodecode.model.db import (
42 42 or_, count, User, UserGroup, UserGroupMember, in_filter_generator)
43 43 from rhodecode.model.meta import Session
44 44 from rhodecode.model.user_group import UserGroupModel
45 45 from rhodecode.model.db import true
46 46
47 47 log = logging.getLogger(__name__)
48 48
49 49
50 50 class AdminUserGroupsView(BaseAppView, DataGridAppView):
51 51
52 52 def load_default_context(self):
53 53 c = self._get_local_tmpl_context()
54
55 54 PermissionModel().set_global_permission_choices(
56 55 c, gettext_translator=self.request.translate)
57
58 56 return c
59 57
60 58 # permission check in data loading of
61 59 # `user_groups_list_data` via UserGroupList
62 60 @LoginRequired()
63 61 @NotAnonymous()
64 @view_config(
65 route_name='user_groups', request_method='GET',
66 renderer='rhodecode:templates/admin/user_groups/user_groups.mako')
67 62 def user_groups_list(self):
68 63 c = self.load_default_context()
69 64 return self._get_template_context(c)
70 65
71 66 # permission check inside
72 67 @LoginRequired()
73 68 @NotAnonymous()
74 @view_config(
75 route_name='user_groups_data', request_method='GET',
76 renderer='json_ext', xhr=True)
77 69 def user_groups_list_data(self):
78 70 self.load_default_context()
79 71 column_map = {
80 72 'active': 'users_group_active',
81 73 'description': 'user_group_description',
82 74 'members': 'members_total',
83 75 'owner': 'user_username',
84 76 'sync': 'group_data'
85 77 }
86 78 draw, start, limit = self._extract_chunk(self.request)
87 79 search_q, order_by, order_dir = self._extract_ordering(
88 80 self.request, column_map=column_map)
89 81
90 82 _render = self.request.get_partial_renderer(
91 83 'rhodecode:templates/data_table/_dt_elements.mako')
92 84
93 85 def user_group_name(user_group_name):
94 86 return _render("user_group_name", user_group_name)
95 87
96 88 def user_group_actions(user_group_id, user_group_name):
97 89 return _render("user_group_actions", user_group_id, user_group_name)
98 90
99 91 def user_profile(username):
100 92 return _render('user_profile', username)
101 93
102 94 _perms = ['usergroup.admin']
103 95 allowed_ids = [-1] + self._rhodecode_user.user_group_acl_ids_from_stack(_perms)
104 96
105 97 user_groups_data_total_count = UserGroup.query()\
106 98 .filter(or_(
107 99 # generate multiple IN to fix limitation problems
108 100 *in_filter_generator(UserGroup.users_group_id, allowed_ids)
109 101 ))\
110 102 .count()
111 103
112 104 user_groups_data_total_inactive_count = UserGroup.query()\
113 105 .filter(or_(
114 106 # generate multiple IN to fix limitation problems
115 107 *in_filter_generator(UserGroup.users_group_id, allowed_ids)
116 108 ))\
117 109 .filter(UserGroup.users_group_active != true()).count()
118 110
119 111 member_count = count(UserGroupMember.user_id)
120 112 base_q = Session.query(
121 113 UserGroup.users_group_name,
122 114 UserGroup.user_group_description,
123 115 UserGroup.users_group_active,
124 116 UserGroup.users_group_id,
125 117 UserGroup.group_data,
126 118 User,
127 119 member_count.label('member_count')
128 120 ) \
129 121 .filter(or_(
130 122 # generate multiple IN to fix limitation problems
131 123 *in_filter_generator(UserGroup.users_group_id, allowed_ids)
132 124 )) \
133 125 .outerjoin(UserGroupMember, UserGroupMember.users_group_id == UserGroup.users_group_id) \
134 126 .join(User, User.user_id == UserGroup.user_id) \
135 127 .group_by(UserGroup, User)
136 128
137 129 base_q_inactive = base_q.filter(UserGroup.users_group_active != true())
138 130
139 131 if search_q:
140 132 like_expression = u'%{}%'.format(safe_unicode(search_q))
141 133 base_q = base_q.filter(or_(
142 134 UserGroup.users_group_name.ilike(like_expression),
143 135 ))
144 136 base_q_inactive = base_q.filter(UserGroup.users_group_active != true())
145 137
146 138 user_groups_data_total_filtered_count = base_q.count()
147 139 user_groups_data_total_filtered_inactive_count = base_q_inactive.count()
148 140
149 141 sort_defined = False
150 142 if order_by == 'members_total':
151 143 sort_col = member_count
152 144 sort_defined = True
153 145 elif order_by == 'user_username':
154 146 sort_col = User.username
155 147 else:
156 148 sort_col = getattr(UserGroup, order_by, None)
157 149
158 150 if sort_defined or sort_col:
159 151 if order_dir == 'asc':
160 152 sort_col = sort_col.asc()
161 153 else:
162 154 sort_col = sort_col.desc()
163 155
164 156 base_q = base_q.order_by(sort_col)
165 157 base_q = base_q.offset(start).limit(limit)
166 158
167 159 # authenticated access to user groups
168 160 auth_user_group_list = base_q.all()
169 161
170 162 user_groups_data = []
171 163 for user_gr in auth_user_group_list:
172 164 row = {
173 165 "users_group_name": user_group_name(user_gr.users_group_name),
174 166 "description": h.escape(user_gr.user_group_description),
175 167 "members": user_gr.member_count,
176 168 # NOTE(marcink): because of advanced query we
177 169 # need to load it like that
178 170 "sync": UserGroup._load_sync(
179 171 UserGroup._load_group_data(user_gr.group_data)),
180 172 "active": h.bool2icon(user_gr.users_group_active),
181 173 "owner": user_profile(user_gr.User.username),
182 174 "action": user_group_actions(
183 175 user_gr.users_group_id, user_gr.users_group_name)
184 176 }
185 177 user_groups_data.append(row)
186 178
187 179 data = ({
188 180 'draw': draw,
189 181 'data': user_groups_data,
190 182 'recordsTotal': user_groups_data_total_count,
191 183 'recordsTotalInactive': user_groups_data_total_inactive_count,
192 184 'recordsFiltered': user_groups_data_total_filtered_count,
193 185 'recordsFilteredInactive': user_groups_data_total_filtered_inactive_count,
194 186 })
195 187
196 188 return data
197 189
198 190 @LoginRequired()
199 191 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
200 @view_config(
201 route_name='user_groups_new', request_method='GET',
202 renderer='rhodecode:templates/admin/user_groups/user_group_add.mako')
203 192 def user_groups_new(self):
204 193 c = self.load_default_context()
205 194 return self._get_template_context(c)
206 195
207 196 @LoginRequired()
208 197 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
209 198 @CSRFRequired()
210 @view_config(
211 route_name='user_groups_create', request_method='POST',
212 renderer='rhodecode:templates/admin/user_groups/user_group_add.mako')
213 199 def user_groups_create(self):
214 200 _ = self.request.translate
215 201 c = self.load_default_context()
216 202 users_group_form = UserGroupForm(self.request.translate)()
217 203
218 204 user_group_name = self.request.POST.get('users_group_name')
219 205 try:
220 206 form_result = users_group_form.to_python(dict(self.request.POST))
221 207 user_group = UserGroupModel().create(
222 208 name=form_result['users_group_name'],
223 209 description=form_result['user_group_description'],
224 210 owner=self._rhodecode_user.user_id,
225 211 active=form_result['users_group_active'])
226 212 Session().flush()
227 213 creation_data = user_group.get_api_data()
228 214 user_group_name = form_result['users_group_name']
229 215
230 216 audit_logger.store_web(
231 217 'user_group.create', action_data={'data': creation_data},
232 218 user=self._rhodecode_user)
233 219
234 220 user_group_link = h.link_to(
235 221 h.escape(user_group_name),
236 222 h.route_path(
237 223 'edit_user_group', user_group_id=user_group.users_group_id))
238 224 h.flash(h.literal(_('Created user group %(user_group_link)s')
239 225 % {'user_group_link': user_group_link}),
240 226 category='success')
241 227 Session().commit()
242 228 user_group_id = user_group.users_group_id
243 229 except formencode.Invalid as errors:
244 230
245 231 data = render(
246 232 'rhodecode:templates/admin/user_groups/user_group_add.mako',
247 233 self._get_template_context(c), self.request)
248 234 html = formencode.htmlfill.render(
249 235 data,
250 236 defaults=errors.value,
251 237 errors=errors.error_dict or {},
252 238 prefix_error=False,
253 239 encoding="UTF-8",
254 240 force_defaults=False
255 241 )
256 242 return Response(html)
257 243
258 244 except Exception:
259 245 log.exception("Exception creating user group")
260 246 h.flash(_('Error occurred during creation of user group %s') \
261 247 % user_group_name, category='error')
262 248 raise HTTPFound(h.route_path('user_groups_new'))
263 249
264 250 affected_user_ids = [self._rhodecode_user.user_id]
265 251 PermissionModel().trigger_permission_flush(affected_user_ids)
266 252
267 253 raise HTTPFound(
268 254 h.route_path('edit_user_group', user_group_id=user_group_id))
@@ -1,1418 +1,1318 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2020 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 from pyramid.view import view_config
28 27 from pyramid.renderers import render
29 28 from pyramid.response import Response
30 29
31 30 from rhodecode import events
32 31 from rhodecode.apps._base import BaseAppView, DataGridAppView, UserAppView
33 32 from rhodecode.apps.ssh_support import SshKeyFileChangeEvent
34 33 from rhodecode.authentication.base import get_authn_registry, RhodeCodeExternalAuthPlugin
35 34 from rhodecode.authentication.plugins import auth_rhodecode
36 35 from rhodecode.events import trigger
37 36 from rhodecode.model.db import true, UserNotice
38 37
39 38 from rhodecode.lib import audit_logger, rc_cache, auth
40 39 from rhodecode.lib.exceptions import (
41 40 UserCreationError, UserOwnsReposException, UserOwnsRepoGroupsException,
42 41 UserOwnsUserGroupsException, UserOwnsPullRequestsException,
43 42 UserOwnsArtifactsException, DefaultUserException)
44 43 from rhodecode.lib.ext_json import json
45 44 from rhodecode.lib.auth import (
46 45 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
47 46 from rhodecode.lib import helpers as h
48 47 from rhodecode.lib.helpers import SqlPage
49 48 from rhodecode.lib.utils2 import safe_int, safe_unicode, AttributeDict
50 49 from rhodecode.model.auth_token import AuthTokenModel
51 50 from rhodecode.model.forms import (
52 51 UserForm, UserIndividualPermissionsForm, UserPermissionsForm,
53 52 UserExtraEmailForm, UserExtraIpForm)
54 53 from rhodecode.model.permission import PermissionModel
55 54 from rhodecode.model.repo_group import RepoGroupModel
56 55 from rhodecode.model.ssh_key import SshKeyModel
57 56 from rhodecode.model.user import UserModel
58 57 from rhodecode.model.user_group import UserGroupModel
59 58 from rhodecode.model.db import (
60 59 or_, coalesce,IntegrityError, User, UserGroup, UserIpMap, UserEmailMap,
61 60 UserApiKeys, UserSshKeys, RepoGroup)
62 61 from rhodecode.model.meta import Session
63 62
64 63 log = logging.getLogger(__name__)
65 64
66 65
67 66 class AdminUsersView(BaseAppView, DataGridAppView):
68 67
69 68 def load_default_context(self):
70 69 c = self._get_local_tmpl_context()
71 70 return c
72 71
73 72 @LoginRequired()
74 73 @HasPermissionAllDecorator('hg.admin')
75 @view_config(
76 route_name='users', request_method='GET',
77 renderer='rhodecode:templates/admin/users/users.mako')
78 74 def users_list(self):
79 75 c = self.load_default_context()
80 76 return self._get_template_context(c)
81 77
82 78 @LoginRequired()
83 79 @HasPermissionAllDecorator('hg.admin')
84 @view_config(
85 # renderer defined below
86 route_name='users_data', request_method='GET',
87 renderer='json_ext', xhr=True)
88 80 def users_list_data(self):
89 81 self.load_default_context()
90 82 column_map = {
91 83 'first_name': 'name',
92 84 'last_name': 'lastname',
93 85 }
94 86 draw, start, limit = self._extract_chunk(self.request)
95 87 search_q, order_by, order_dir = self._extract_ordering(
96 88 self.request, column_map=column_map)
97 89 _render = self.request.get_partial_renderer(
98 90 'rhodecode:templates/data_table/_dt_elements.mako')
99 91
100 92 def user_actions(user_id, username):
101 93 return _render("user_actions", user_id, username)
102 94
103 95 users_data_total_count = User.query()\
104 96 .filter(User.username != User.DEFAULT_USER) \
105 97 .count()
106 98
107 99 users_data_total_inactive_count = User.query()\
108 100 .filter(User.username != User.DEFAULT_USER) \
109 101 .filter(User.active != true())\
110 102 .count()
111 103
112 104 # json generate
113 105 base_q = User.query().filter(User.username != User.DEFAULT_USER)
114 106 base_inactive_q = base_q.filter(User.active != true())
115 107
116 108 if search_q:
117 109 like_expression = u'%{}%'.format(safe_unicode(search_q))
118 110 base_q = base_q.filter(or_(
119 111 User.username.ilike(like_expression),
120 112 User._email.ilike(like_expression),
121 113 User.name.ilike(like_expression),
122 114 User.lastname.ilike(like_expression),
123 115 ))
124 116 base_inactive_q = base_q.filter(User.active != true())
125 117
126 118 users_data_total_filtered_count = base_q.count()
127 119 users_data_total_filtered_inactive_count = base_inactive_q.count()
128 120
129 121 sort_col = getattr(User, order_by, None)
130 122 if sort_col:
131 123 if order_dir == 'asc':
132 124 # handle null values properly to order by NULL last
133 125 if order_by in ['last_activity']:
134 126 sort_col = coalesce(sort_col, datetime.date.max)
135 127 sort_col = sort_col.asc()
136 128 else:
137 129 # handle null values properly to order by NULL last
138 130 if order_by in ['last_activity']:
139 131 sort_col = coalesce(sort_col, datetime.date.min)
140 132 sort_col = sort_col.desc()
141 133
142 134 base_q = base_q.order_by(sort_col)
143 135 base_q = base_q.offset(start).limit(limit)
144 136
145 137 users_list = base_q.all()
146 138
147 139 users_data = []
148 140 for user in users_list:
149 141 users_data.append({
150 142 "username": h.gravatar_with_user(self.request, user.username),
151 143 "email": user.email,
152 144 "first_name": user.first_name,
153 145 "last_name": user.last_name,
154 146 "last_login": h.format_date(user.last_login),
155 147 "last_activity": h.format_date(user.last_activity),
156 148 "active": h.bool2icon(user.active),
157 149 "active_raw": user.active,
158 150 "admin": h.bool2icon(user.admin),
159 151 "extern_type": user.extern_type,
160 152 "extern_name": user.extern_name,
161 153 "action": user_actions(user.user_id, user.username),
162 154 })
163 155 data = ({
164 156 'draw': draw,
165 157 'data': users_data,
166 158 'recordsTotal': users_data_total_count,
167 159 'recordsFiltered': users_data_total_filtered_count,
168 160 'recordsTotalInactive': users_data_total_inactive_count,
169 161 'recordsFilteredInactive': users_data_total_filtered_inactive_count
170 162 })
171 163
172 164 return data
173 165
174 166 def _set_personal_repo_group_template_vars(self, c_obj):
175 167 DummyUser = AttributeDict({
176 168 'username': '${username}',
177 169 'user_id': '${user_id}',
178 170 })
179 171 c_obj.default_create_repo_group = RepoGroupModel() \
180 172 .get_default_create_personal_repo_group()
181 173 c_obj.personal_repo_group_name = RepoGroupModel() \
182 174 .get_personal_group_name(DummyUser)
183 175
184 176 @LoginRequired()
185 177 @HasPermissionAllDecorator('hg.admin')
186 @view_config(
187 route_name='users_new', request_method='GET',
188 renderer='rhodecode:templates/admin/users/user_add.mako')
189 178 def users_new(self):
190 179 _ = self.request.translate
191 180 c = self.load_default_context()
192 181 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.uid
193 182 self._set_personal_repo_group_template_vars(c)
194 183 return self._get_template_context(c)
195 184
196 185 @LoginRequired()
197 186 @HasPermissionAllDecorator('hg.admin')
198 187 @CSRFRequired()
199 @view_config(
200 route_name='users_create', request_method='POST',
201 renderer='rhodecode:templates/admin/users/user_add.mako')
202 188 def users_create(self):
203 189 _ = self.request.translate
204 190 c = self.load_default_context()
205 191 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.uid
206 192 user_model = UserModel()
207 193 user_form = UserForm(self.request.translate)()
208 194 try:
209 195 form_result = user_form.to_python(dict(self.request.POST))
210 196 user = user_model.create(form_result)
211 197 Session().flush()
212 198 creation_data = user.get_api_data()
213 199 username = form_result['username']
214 200
215 201 audit_logger.store_web(
216 202 'user.create', action_data={'data': creation_data},
217 203 user=c.rhodecode_user)
218 204
219 205 user_link = h.link_to(
220 206 h.escape(username),
221 207 h.route_path('user_edit', user_id=user.user_id))
222 208 h.flash(h.literal(_('Created user %(user_link)s')
223 209 % {'user_link': user_link}), category='success')
224 210 Session().commit()
225 211 except formencode.Invalid as errors:
226 212 self._set_personal_repo_group_template_vars(c)
227 213 data = render(
228 214 'rhodecode:templates/admin/users/user_add.mako',
229 215 self._get_template_context(c), self.request)
230 216 html = formencode.htmlfill.render(
231 217 data,
232 218 defaults=errors.value,
233 219 errors=errors.error_dict or {},
234 220 prefix_error=False,
235 221 encoding="UTF-8",
236 222 force_defaults=False
237 223 )
238 224 return Response(html)
239 225 except UserCreationError as e:
240 226 h.flash(e, 'error')
241 227 except Exception:
242 228 log.exception("Exception creation of user")
243 229 h.flash(_('Error occurred during creation of user %s')
244 230 % self.request.POST.get('username'), category='error')
245 231 raise HTTPFound(h.route_path('users'))
246 232
247 233
248 234 class UsersView(UserAppView):
249 235 ALLOW_SCOPED_TOKENS = False
250 236 """
251 237 This view has alternative version inside EE, if modified please take a look
252 238 in there as well.
253 239 """
254 240
255 241 def get_auth_plugins(self):
256 242 valid_plugins = []
257 243 authn_registry = get_authn_registry(self.request.registry)
258 244 for plugin in authn_registry.get_plugins_for_authentication():
259 245 if isinstance(plugin, RhodeCodeExternalAuthPlugin):
260 246 valid_plugins.append(plugin)
261 247 elif plugin.name == 'rhodecode':
262 248 valid_plugins.append(plugin)
263 249
264 250 # extend our choices if user has set a bound plugin which isn't enabled at the
265 251 # moment
266 252 extern_type = self.db_user.extern_type
267 253 if extern_type not in [x.uid for x in valid_plugins]:
268 254 try:
269 255 plugin = authn_registry.get_plugin_by_uid(extern_type)
270 256 if plugin:
271 257 valid_plugins.append(plugin)
272 258
273 259 except Exception:
274 260 log.exception(
275 261 'Could not extend user plugins with `{}`'.format(extern_type))
276 262 return valid_plugins
277 263
278 264 def load_default_context(self):
279 265 req = self.request
280 266
281 267 c = self._get_local_tmpl_context()
282 268 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
283 269 c.allowed_languages = [
284 270 ('en', 'English (en)'),
285 271 ('de', 'German (de)'),
286 272 ('fr', 'French (fr)'),
287 273 ('it', 'Italian (it)'),
288 274 ('ja', 'Japanese (ja)'),
289 275 ('pl', 'Polish (pl)'),
290 276 ('pt', 'Portuguese (pt)'),
291 277 ('ru', 'Russian (ru)'),
292 278 ('zh', 'Chinese (zh)'),
293 279 ]
294 280
295 281 c.allowed_extern_types = [
296 282 (x.uid, x.get_display_name()) for x in self.get_auth_plugins()
297 283 ]
298 284 perms = req.registry.settings.get('available_permissions')
299 285 if not perms:
300 286 # inject info about available permissions
301 287 auth.set_available_permissions(req.registry.settings)
302 288
303 289 c.available_permissions = req.registry.settings['available_permissions']
304 290 PermissionModel().set_global_permission_choices(
305 291 c, gettext_translator=req.translate)
306 292
307 293 return c
308 294
309 295 @LoginRequired()
310 296 @HasPermissionAllDecorator('hg.admin')
311 297 @CSRFRequired()
312 @view_config(
313 route_name='user_update', request_method='POST',
314 renderer='rhodecode:templates/admin/users/user_edit.mako')
315 298 def user_update(self):
316 299 _ = self.request.translate
317 300 c = self.load_default_context()
318 301
319 302 user_id = self.db_user_id
320 303 c.user = self.db_user
321 304
322 305 c.active = 'profile'
323 306 c.extern_type = c.user.extern_type
324 307 c.extern_name = c.user.extern_name
325 308 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
326 309 available_languages = [x[0] for x in c.allowed_languages]
327 310 _form = UserForm(self.request.translate, edit=True,
328 311 available_languages=available_languages,
329 312 old_data={'user_id': user_id,
330 313 'email': c.user.email})()
331 314 form_result = {}
332 315 old_values = c.user.get_api_data()
333 316 try:
334 317 form_result = _form.to_python(dict(self.request.POST))
335 318 skip_attrs = ['extern_name']
336 319 # TODO: plugin should define if username can be updated
337 320 if c.extern_type != "rhodecode":
338 321 # forbid updating username for external accounts
339 322 skip_attrs.append('username')
340 323
341 324 UserModel().update_user(
342 325 user_id, skip_attrs=skip_attrs, **form_result)
343 326
344 327 audit_logger.store_web(
345 328 'user.edit', action_data={'old_data': old_values},
346 329 user=c.rhodecode_user)
347 330
348 331 Session().commit()
349 332 h.flash(_('User updated successfully'), category='success')
350 333 except formencode.Invalid as errors:
351 334 data = render(
352 335 'rhodecode:templates/admin/users/user_edit.mako',
353 336 self._get_template_context(c), self.request)
354 337 html = formencode.htmlfill.render(
355 338 data,
356 339 defaults=errors.value,
357 340 errors=errors.error_dict or {},
358 341 prefix_error=False,
359 342 encoding="UTF-8",
360 343 force_defaults=False
361 344 )
362 345 return Response(html)
363 346 except UserCreationError as e:
364 347 h.flash(e, 'error')
365 348 except Exception:
366 349 log.exception("Exception updating user")
367 350 h.flash(_('Error occurred during update of user %s')
368 351 % form_result.get('username'), category='error')
369 352 raise HTTPFound(h.route_path('user_edit', user_id=user_id))
370 353
371 354 @LoginRequired()
372 355 @HasPermissionAllDecorator('hg.admin')
373 356 @CSRFRequired()
374 @view_config(
375 route_name='user_delete', request_method='POST',
376 renderer='rhodecode:templates/admin/users/user_edit.mako')
377 357 def user_delete(self):
378 358 _ = self.request.translate
379 359 c = self.load_default_context()
380 360 c.user = self.db_user
381 361
382 362 _repos = c.user.repositories
383 363 _repo_groups = c.user.repository_groups
384 364 _user_groups = c.user.user_groups
385 365 _pull_requests = c.user.user_pull_requests
386 366 _artifacts = c.user.artifacts
387 367
388 368 handle_repos = None
389 369 handle_repo_groups = None
390 370 handle_user_groups = None
391 371 handle_pull_requests = None
392 372 handle_artifacts = None
393 373
394 374 # calls for flash of handle based on handle case detach or delete
395 375 def set_handle_flash_repos():
396 376 handle = handle_repos
397 377 if handle == 'detach':
398 378 h.flash(_('Detached %s repositories') % len(_repos),
399 379 category='success')
400 380 elif handle == 'delete':
401 381 h.flash(_('Deleted %s repositories') % len(_repos),
402 382 category='success')
403 383
404 384 def set_handle_flash_repo_groups():
405 385 handle = handle_repo_groups
406 386 if handle == 'detach':
407 387 h.flash(_('Detached %s repository groups') % len(_repo_groups),
408 388 category='success')
409 389 elif handle == 'delete':
410 390 h.flash(_('Deleted %s repository groups') % len(_repo_groups),
411 391 category='success')
412 392
413 393 def set_handle_flash_user_groups():
414 394 handle = handle_user_groups
415 395 if handle == 'detach':
416 396 h.flash(_('Detached %s user groups') % len(_user_groups),
417 397 category='success')
418 398 elif handle == 'delete':
419 399 h.flash(_('Deleted %s user groups') % len(_user_groups),
420 400 category='success')
421 401
422 402 def set_handle_flash_pull_requests():
423 403 handle = handle_pull_requests
424 404 if handle == 'detach':
425 405 h.flash(_('Detached %s pull requests') % len(_pull_requests),
426 406 category='success')
427 407 elif handle == 'delete':
428 408 h.flash(_('Deleted %s pull requests') % len(_pull_requests),
429 409 category='success')
430 410
431 411 def set_handle_flash_artifacts():
432 412 handle = handle_artifacts
433 413 if handle == 'detach':
434 414 h.flash(_('Detached %s artifacts') % len(_artifacts),
435 415 category='success')
436 416 elif handle == 'delete':
437 417 h.flash(_('Deleted %s artifacts') % len(_artifacts),
438 418 category='success')
439 419
440 420 handle_user = User.get_first_super_admin()
441 421 handle_user_id = safe_int(self.request.POST.get('detach_user_id'))
442 422 if handle_user_id:
443 423 # NOTE(marcink): we get new owner for objects...
444 424 handle_user = User.get_or_404(handle_user_id)
445 425
446 426 if _repos and self.request.POST.get('user_repos'):
447 427 handle_repos = self.request.POST['user_repos']
448 428
449 429 if _repo_groups and self.request.POST.get('user_repo_groups'):
450 430 handle_repo_groups = self.request.POST['user_repo_groups']
451 431
452 432 if _user_groups and self.request.POST.get('user_user_groups'):
453 433 handle_user_groups = self.request.POST['user_user_groups']
454 434
455 435 if _pull_requests and self.request.POST.get('user_pull_requests'):
456 436 handle_pull_requests = self.request.POST['user_pull_requests']
457 437
458 438 if _artifacts and self.request.POST.get('user_artifacts'):
459 439 handle_artifacts = self.request.POST['user_artifacts']
460 440
461 441 old_values = c.user.get_api_data()
462 442
463 443 try:
464 444
465 445 UserModel().delete(
466 446 c.user,
467 447 handle_repos=handle_repos,
468 448 handle_repo_groups=handle_repo_groups,
469 449 handle_user_groups=handle_user_groups,
470 450 handle_pull_requests=handle_pull_requests,
471 451 handle_artifacts=handle_artifacts,
472 452 handle_new_owner=handle_user
473 453 )
474 454
475 455 audit_logger.store_web(
476 456 'user.delete', action_data={'old_data': old_values},
477 457 user=c.rhodecode_user)
478 458
479 459 Session().commit()
480 460 set_handle_flash_repos()
481 461 set_handle_flash_repo_groups()
482 462 set_handle_flash_user_groups()
483 463 set_handle_flash_pull_requests()
484 464 set_handle_flash_artifacts()
485 465 username = h.escape(old_values['username'])
486 466 h.flash(_('Successfully deleted user `{}`').format(username), category='success')
487 467 except (UserOwnsReposException, UserOwnsRepoGroupsException,
488 468 UserOwnsUserGroupsException, UserOwnsPullRequestsException,
489 469 UserOwnsArtifactsException, DefaultUserException) as e:
490 470 h.flash(e, category='warning')
491 471 except Exception:
492 472 log.exception("Exception during deletion of user")
493 473 h.flash(_('An error occurred during deletion of user'),
494 474 category='error')
495 475 raise HTTPFound(h.route_path('users'))
496 476
497 477 @LoginRequired()
498 478 @HasPermissionAllDecorator('hg.admin')
499 @view_config(
500 route_name='user_edit', request_method='GET',
501 renderer='rhodecode:templates/admin/users/user_edit.mako')
502 479 def user_edit(self):
503 480 _ = self.request.translate
504 481 c = self.load_default_context()
505 482 c.user = self.db_user
506 483
507 484 c.active = 'profile'
508 485 c.extern_type = c.user.extern_type
509 486 c.extern_name = c.user.extern_name
510 487 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
511 488
512 489 defaults = c.user.get_dict()
513 490 defaults.update({'language': c.user.user_data.get('language')})
514 491
515 492 data = render(
516 493 'rhodecode:templates/admin/users/user_edit.mako',
517 494 self._get_template_context(c), self.request)
518 495 html = formencode.htmlfill.render(
519 496 data,
520 497 defaults=defaults,
521 498 encoding="UTF-8",
522 499 force_defaults=False
523 500 )
524 501 return Response(html)
525 502
526 503 @LoginRequired()
527 504 @HasPermissionAllDecorator('hg.admin')
528 @view_config(
529 route_name='user_edit_advanced', request_method='GET',
530 renderer='rhodecode:templates/admin/users/user_edit.mako')
531 505 def user_edit_advanced(self):
532 506 _ = self.request.translate
533 507 c = self.load_default_context()
534 508
535 509 user_id = self.db_user_id
536 510 c.user = self.db_user
537 511
538 512 c.detach_user = User.get_first_super_admin()
539 513 detach_user_id = safe_int(self.request.GET.get('detach_user_id'))
540 514 if detach_user_id:
541 515 c.detach_user = User.get_or_404(detach_user_id)
542 516
543 517 c.active = 'advanced'
544 518 c.personal_repo_group = RepoGroup.get_user_personal_repo_group(user_id)
545 519 c.personal_repo_group_name = RepoGroupModel()\
546 520 .get_personal_group_name(c.user)
547 521
548 522 c.user_to_review_rules = sorted(
549 523 (x.user for x in c.user.user_review_rules),
550 524 key=lambda u: u.username.lower())
551 525
552 526 defaults = c.user.get_dict()
553 527
554 528 # Interim workaround if the user participated on any pull requests as a
555 529 # reviewer.
556 530 has_review = len(c.user.reviewer_pull_requests)
557 531 c.can_delete_user = not has_review
558 532 c.can_delete_user_message = ''
559 533 inactive_link = h.link_to(
560 534 'inactive', h.route_path('user_edit', user_id=user_id, _anchor='active'))
561 535 if has_review == 1:
562 536 c.can_delete_user_message = h.literal(_(
563 537 'The user participates as reviewer in {} pull request and '
564 538 'cannot be deleted. \nYou can set the user to '
565 539 '"{}" instead of deleting it.').format(
566 540 has_review, inactive_link))
567 541 elif has_review:
568 542 c.can_delete_user_message = h.literal(_(
569 543 'The user participates as reviewer in {} pull requests and '
570 544 'cannot be deleted. \nYou can set the user to '
571 545 '"{}" instead of deleting it.').format(
572 546 has_review, inactive_link))
573 547
574 548 data = render(
575 549 'rhodecode:templates/admin/users/user_edit.mako',
576 550 self._get_template_context(c), self.request)
577 551 html = formencode.htmlfill.render(
578 552 data,
579 553 defaults=defaults,
580 554 encoding="UTF-8",
581 555 force_defaults=False
582 556 )
583 557 return Response(html)
584 558
585 559 @LoginRequired()
586 560 @HasPermissionAllDecorator('hg.admin')
587 @view_config(
588 route_name='user_edit_global_perms', request_method='GET',
589 renderer='rhodecode:templates/admin/users/user_edit.mako')
590 561 def user_edit_global_perms(self):
591 562 _ = self.request.translate
592 563 c = self.load_default_context()
593 564 c.user = self.db_user
594 565
595 566 c.active = 'global_perms'
596 567
597 568 c.default_user = User.get_default_user()
598 569 defaults = c.user.get_dict()
599 570 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
600 571 defaults.update(c.default_user.get_default_perms())
601 572 defaults.update(c.user.get_default_perms())
602 573
603 574 data = render(
604 575 'rhodecode:templates/admin/users/user_edit.mako',
605 576 self._get_template_context(c), self.request)
606 577 html = formencode.htmlfill.render(
607 578 data,
608 579 defaults=defaults,
609 580 encoding="UTF-8",
610 581 force_defaults=False
611 582 )
612 583 return Response(html)
613 584
614 585 @LoginRequired()
615 586 @HasPermissionAllDecorator('hg.admin')
616 587 @CSRFRequired()
617 @view_config(
618 route_name='user_edit_global_perms_update', request_method='POST',
619 renderer='rhodecode:templates/admin/users/user_edit.mako')
620 588 def user_edit_global_perms_update(self):
621 589 _ = self.request.translate
622 590 c = self.load_default_context()
623 591
624 592 user_id = self.db_user_id
625 593 c.user = self.db_user
626 594
627 595 c.active = 'global_perms'
628 596 try:
629 597 # first stage that verifies the checkbox
630 598 _form = UserIndividualPermissionsForm(self.request.translate)
631 599 form_result = _form.to_python(dict(self.request.POST))
632 600 inherit_perms = form_result['inherit_default_permissions']
633 601 c.user.inherit_default_permissions = inherit_perms
634 602 Session().add(c.user)
635 603
636 604 if not inherit_perms:
637 605 # only update the individual ones if we un check the flag
638 606 _form = UserPermissionsForm(
639 607 self.request.translate,
640 608 [x[0] for x in c.repo_create_choices],
641 609 [x[0] for x in c.repo_create_on_write_choices],
642 610 [x[0] for x in c.repo_group_create_choices],
643 611 [x[0] for x in c.user_group_create_choices],
644 612 [x[0] for x in c.fork_choices],
645 613 [x[0] for x in c.inherit_default_permission_choices])()
646 614
647 615 form_result = _form.to_python(dict(self.request.POST))
648 616 form_result.update({'perm_user_id': c.user.user_id})
649 617
650 618 PermissionModel().update_user_permissions(form_result)
651 619
652 620 # TODO(marcink): implement global permissions
653 621 # audit_log.store_web('user.edit.permissions')
654 622
655 623 Session().commit()
656 624
657 625 h.flash(_('User global permissions updated successfully'),
658 626 category='success')
659 627
660 628 except formencode.Invalid as errors:
661 629 data = render(
662 630 'rhodecode:templates/admin/users/user_edit.mako',
663 631 self._get_template_context(c), self.request)
664 632 html = formencode.htmlfill.render(
665 633 data,
666 634 defaults=errors.value,
667 635 errors=errors.error_dict or {},
668 636 prefix_error=False,
669 637 encoding="UTF-8",
670 638 force_defaults=False
671 639 )
672 640 return Response(html)
673 641 except Exception:
674 642 log.exception("Exception during permissions saving")
675 643 h.flash(_('An error occurred during permissions saving'),
676 644 category='error')
677 645
678 646 affected_user_ids = [user_id]
679 647 PermissionModel().trigger_permission_flush(affected_user_ids)
680 648 raise HTTPFound(h.route_path('user_edit_global_perms', user_id=user_id))
681 649
682 650 @LoginRequired()
683 651 @HasPermissionAllDecorator('hg.admin')
684 652 @CSRFRequired()
685 @view_config(
686 route_name='user_enable_force_password_reset', request_method='POST',
687 renderer='rhodecode:templates/admin/users/user_edit.mako')
688 653 def user_enable_force_password_reset(self):
689 654 _ = self.request.translate
690 655 c = self.load_default_context()
691 656
692 657 user_id = self.db_user_id
693 658 c.user = self.db_user
694 659
695 660 try:
696 661 c.user.update_userdata(force_password_change=True)
697 662
698 663 msg = _('Force password change enabled for user')
699 664 audit_logger.store_web('user.edit.password_reset.enabled',
700 665 user=c.rhodecode_user)
701 666
702 667 Session().commit()
703 668 h.flash(msg, category='success')
704 669 except Exception:
705 670 log.exception("Exception during password reset for user")
706 671 h.flash(_('An error occurred during password reset for user'),
707 672 category='error')
708 673
709 674 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
710 675
711 676 @LoginRequired()
712 677 @HasPermissionAllDecorator('hg.admin')
713 678 @CSRFRequired()
714 @view_config(
715 route_name='user_disable_force_password_reset', request_method='POST',
716 renderer='rhodecode:templates/admin/users/user_edit.mako')
717 679 def user_disable_force_password_reset(self):
718 680 _ = self.request.translate
719 681 c = self.load_default_context()
720 682
721 683 user_id = self.db_user_id
722 684 c.user = self.db_user
723 685
724 686 try:
725 687 c.user.update_userdata(force_password_change=False)
726 688
727 689 msg = _('Force password change disabled for user')
728 690 audit_logger.store_web(
729 691 'user.edit.password_reset.disabled',
730 692 user=c.rhodecode_user)
731 693
732 694 Session().commit()
733 695 h.flash(msg, category='success')
734 696 except Exception:
735 697 log.exception("Exception during password reset for user")
736 698 h.flash(_('An error occurred during password reset for user'),
737 699 category='error')
738 700
739 701 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
740 702
741 703 @LoginRequired()
742 704 @HasPermissionAllDecorator('hg.admin')
743 705 @CSRFRequired()
744 @view_config(
745 route_name='user_notice_dismiss', request_method='POST',
746 renderer='json_ext', xhr=True)
747 706 def user_notice_dismiss(self):
748 707 _ = self.request.translate
749 708 c = self.load_default_context()
750 709
751 710 user_id = self.db_user_id
752 711 c.user = self.db_user
753 712 user_notice_id = safe_int(self.request.POST.get('notice_id'))
754 713 notice = UserNotice().query()\
755 714 .filter(UserNotice.user_id == user_id)\
756 715 .filter(UserNotice.user_notice_id == user_notice_id)\
757 716 .scalar()
758 717 read = False
759 718 if notice:
760 719 notice.notice_read = True
761 720 Session().add(notice)
762 721 Session().commit()
763 722 read = True
764 723
765 724 return {'notice': user_notice_id, 'read': read}
766 725
767 726 @LoginRequired()
768 727 @HasPermissionAllDecorator('hg.admin')
769 728 @CSRFRequired()
770 @view_config(
771 route_name='user_create_personal_repo_group', request_method='POST',
772 renderer='rhodecode:templates/admin/users/user_edit.mako')
773 729 def user_create_personal_repo_group(self):
774 730 """
775 731 Create personal repository group for this user
776 732 """
777 733 from rhodecode.model.repo_group import RepoGroupModel
778 734
779 735 _ = self.request.translate
780 736 c = self.load_default_context()
781 737
782 738 user_id = self.db_user_id
783 739 c.user = self.db_user
784 740
785 741 personal_repo_group = RepoGroup.get_user_personal_repo_group(
786 742 c.user.user_id)
787 743 if personal_repo_group:
788 744 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
789 745
790 746 personal_repo_group_name = RepoGroupModel().get_personal_group_name(c.user)
791 747 named_personal_group = RepoGroup.get_by_group_name(
792 748 personal_repo_group_name)
793 749 try:
794 750
795 751 if named_personal_group and named_personal_group.user_id == c.user.user_id:
796 752 # migrate the same named group, and mark it as personal
797 753 named_personal_group.personal = True
798 754 Session().add(named_personal_group)
799 755 Session().commit()
800 756 msg = _('Linked repository group `%s` as personal' % (
801 757 personal_repo_group_name,))
802 758 h.flash(msg, category='success')
803 759 elif not named_personal_group:
804 760 RepoGroupModel().create_personal_repo_group(c.user)
805 761
806 762 msg = _('Created repository group `%s`' % (
807 763 personal_repo_group_name,))
808 764 h.flash(msg, category='success')
809 765 else:
810 766 msg = _('Repository group `%s` is already taken' % (
811 767 personal_repo_group_name,))
812 768 h.flash(msg, category='warning')
813 769 except Exception:
814 770 log.exception("Exception during repository group creation")
815 771 msg = _(
816 772 'An error occurred during repository group creation for user')
817 773 h.flash(msg, category='error')
818 774 Session().rollback()
819 775
820 776 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
821 777
822 778 @LoginRequired()
823 779 @HasPermissionAllDecorator('hg.admin')
824 @view_config(
825 route_name='edit_user_auth_tokens', request_method='GET',
826 renderer='rhodecode:templates/admin/users/user_edit.mako')
827 780 def auth_tokens(self):
828 781 _ = self.request.translate
829 782 c = self.load_default_context()
830 783 c.user = self.db_user
831 784
832 785 c.active = 'auth_tokens'
833 786
834 787 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
835 788 c.role_values = [
836 789 (x, AuthTokenModel.cls._get_role_name(x))
837 790 for x in AuthTokenModel.cls.ROLES]
838 791 c.role_options = [(c.role_values, _("Role"))]
839 792 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
840 793 c.user.user_id, show_expired=True)
841 794 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
842 795 return self._get_template_context(c)
843 796
844 797 @LoginRequired()
845 798 @HasPermissionAllDecorator('hg.admin')
846 @view_config(
847 route_name='edit_user_auth_tokens_view', request_method='POST',
848 renderer='json_ext', xhr=True)
849 799 def auth_tokens_view(self):
850 800 _ = self.request.translate
851 801 c = self.load_default_context()
852 802 c.user = self.db_user
853 803
854 804 auth_token_id = self.request.POST.get('auth_token_id')
855 805
856 806 if auth_token_id:
857 807 token = UserApiKeys.get_or_404(auth_token_id)
858 808
859 809 return {
860 810 'auth_token': token.api_key
861 811 }
862 812
863 813 def maybe_attach_token_scope(self, token):
864 814 # implemented in EE edition
865 815 pass
866 816
867 817 @LoginRequired()
868 818 @HasPermissionAllDecorator('hg.admin')
869 819 @CSRFRequired()
870 @view_config(
871 route_name='edit_user_auth_tokens_add', request_method='POST')
872 820 def auth_tokens_add(self):
873 821 _ = self.request.translate
874 822 c = self.load_default_context()
875 823
876 824 user_id = self.db_user_id
877 825 c.user = self.db_user
878 826
879 827 user_data = c.user.get_api_data()
880 828 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
881 829 description = self.request.POST.get('description')
882 830 role = self.request.POST.get('role')
883 831
884 832 token = UserModel().add_auth_token(
885 833 user=c.user.user_id,
886 834 lifetime_minutes=lifetime, role=role, description=description,
887 835 scope_callback=self.maybe_attach_token_scope)
888 836 token_data = token.get_api_data()
889 837
890 838 audit_logger.store_web(
891 839 'user.edit.token.add', action_data={
892 840 'data': {'token': token_data, 'user': user_data}},
893 841 user=self._rhodecode_user, )
894 842 Session().commit()
895 843
896 844 h.flash(_("Auth token successfully created"), category='success')
897 845 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
898 846
899 847 @LoginRequired()
900 848 @HasPermissionAllDecorator('hg.admin')
901 849 @CSRFRequired()
902 @view_config(
903 route_name='edit_user_auth_tokens_delete', request_method='POST')
904 850 def auth_tokens_delete(self):
905 851 _ = self.request.translate
906 852 c = self.load_default_context()
907 853
908 854 user_id = self.db_user_id
909 855 c.user = self.db_user
910 856
911 857 user_data = c.user.get_api_data()
912 858
913 859 del_auth_token = self.request.POST.get('del_auth_token')
914 860
915 861 if del_auth_token:
916 862 token = UserApiKeys.get_or_404(del_auth_token)
917 863 token_data = token.get_api_data()
918 864
919 865 AuthTokenModel().delete(del_auth_token, c.user.user_id)
920 866 audit_logger.store_web(
921 867 'user.edit.token.delete', action_data={
922 868 'data': {'token': token_data, 'user': user_data}},
923 869 user=self._rhodecode_user,)
924 870 Session().commit()
925 871 h.flash(_("Auth token successfully deleted"), category='success')
926 872
927 873 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
928 874
929 875 @LoginRequired()
930 876 @HasPermissionAllDecorator('hg.admin')
931 @view_config(
932 route_name='edit_user_ssh_keys', request_method='GET',
933 renderer='rhodecode:templates/admin/users/user_edit.mako')
934 877 def ssh_keys(self):
935 878 _ = self.request.translate
936 879 c = self.load_default_context()
937 880 c.user = self.db_user
938 881
939 882 c.active = 'ssh_keys'
940 883 c.default_key = self.request.GET.get('default_key')
941 884 c.user_ssh_keys = SshKeyModel().get_ssh_keys(c.user.user_id)
942 885 return self._get_template_context(c)
943 886
944 887 @LoginRequired()
945 888 @HasPermissionAllDecorator('hg.admin')
946 @view_config(
947 route_name='edit_user_ssh_keys_generate_keypair', request_method='GET',
948 renderer='rhodecode:templates/admin/users/user_edit.mako')
949 889 def ssh_keys_generate_keypair(self):
950 890 _ = self.request.translate
951 891 c = self.load_default_context()
952 892
953 893 c.user = self.db_user
954 894
955 895 c.active = 'ssh_keys_generate'
956 896 comment = 'RhodeCode-SSH {}'.format(c.user.email or '')
957 897 private_format = self.request.GET.get('private_format') \
958 898 or SshKeyModel.DEFAULT_PRIVATE_KEY_FORMAT
959 899 c.private, c.public = SshKeyModel().generate_keypair(
960 900 comment=comment, private_format=private_format)
961 901
962 902 return self._get_template_context(c)
963 903
964 904 @LoginRequired()
965 905 @HasPermissionAllDecorator('hg.admin')
966 906 @CSRFRequired()
967 @view_config(
968 route_name='edit_user_ssh_keys_add', request_method='POST')
969 907 def ssh_keys_add(self):
970 908 _ = self.request.translate
971 909 c = self.load_default_context()
972 910
973 911 user_id = self.db_user_id
974 912 c.user = self.db_user
975 913
976 914 user_data = c.user.get_api_data()
977 915 key_data = self.request.POST.get('key_data')
978 916 description = self.request.POST.get('description')
979 917
980 918 fingerprint = 'unknown'
981 919 try:
982 920 if not key_data:
983 921 raise ValueError('Please add a valid public key')
984 922
985 923 key = SshKeyModel().parse_key(key_data.strip())
986 924 fingerprint = key.hash_md5()
987 925
988 926 ssh_key = SshKeyModel().create(
989 927 c.user.user_id, fingerprint, key.keydata, description)
990 928 ssh_key_data = ssh_key.get_api_data()
991 929
992 930 audit_logger.store_web(
993 931 'user.edit.ssh_key.add', action_data={
994 932 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
995 933 user=self._rhodecode_user, )
996 934 Session().commit()
997 935
998 936 # Trigger an event on change of keys.
999 937 trigger(SshKeyFileChangeEvent(), self.request.registry)
1000 938
1001 939 h.flash(_("Ssh Key successfully created"), category='success')
1002 940
1003 941 except IntegrityError:
1004 942 log.exception("Exception during ssh key saving")
1005 943 err = 'Such key with fingerprint `{}` already exists, ' \
1006 944 'please use a different one'.format(fingerprint)
1007 945 h.flash(_('An error occurred during ssh key saving: {}').format(err),
1008 946 category='error')
1009 947 except Exception as e:
1010 948 log.exception("Exception during ssh key saving")
1011 949 h.flash(_('An error occurred during ssh key saving: {}').format(e),
1012 950 category='error')
1013 951
1014 952 return HTTPFound(
1015 953 h.route_path('edit_user_ssh_keys', user_id=user_id))
1016 954
1017 955 @LoginRequired()
1018 956 @HasPermissionAllDecorator('hg.admin')
1019 957 @CSRFRequired()
1020 @view_config(
1021 route_name='edit_user_ssh_keys_delete', request_method='POST')
1022 958 def ssh_keys_delete(self):
1023 959 _ = self.request.translate
1024 960 c = self.load_default_context()
1025 961
1026 962 user_id = self.db_user_id
1027 963 c.user = self.db_user
1028 964
1029 965 user_data = c.user.get_api_data()
1030 966
1031 967 del_ssh_key = self.request.POST.get('del_ssh_key')
1032 968
1033 969 if del_ssh_key:
1034 970 ssh_key = UserSshKeys.get_or_404(del_ssh_key)
1035 971 ssh_key_data = ssh_key.get_api_data()
1036 972
1037 973 SshKeyModel().delete(del_ssh_key, c.user.user_id)
1038 974 audit_logger.store_web(
1039 975 'user.edit.ssh_key.delete', action_data={
1040 976 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
1041 977 user=self._rhodecode_user,)
1042 978 Session().commit()
1043 979 # Trigger an event on change of keys.
1044 980 trigger(SshKeyFileChangeEvent(), self.request.registry)
1045 981 h.flash(_("Ssh key successfully deleted"), category='success')
1046 982
1047 983 return HTTPFound(h.route_path('edit_user_ssh_keys', user_id=user_id))
1048 984
1049 985 @LoginRequired()
1050 986 @HasPermissionAllDecorator('hg.admin')
1051 @view_config(
1052 route_name='edit_user_emails', request_method='GET',
1053 renderer='rhodecode:templates/admin/users/user_edit.mako')
1054 987 def emails(self):
1055 988 _ = self.request.translate
1056 989 c = self.load_default_context()
1057 990 c.user = self.db_user
1058 991
1059 992 c.active = 'emails'
1060 993 c.user_email_map = UserEmailMap.query() \
1061 994 .filter(UserEmailMap.user == c.user).all()
1062 995
1063 996 return self._get_template_context(c)
1064 997
1065 998 @LoginRequired()
1066 999 @HasPermissionAllDecorator('hg.admin')
1067 1000 @CSRFRequired()
1068 @view_config(
1069 route_name='edit_user_emails_add', request_method='POST')
1070 1001 def emails_add(self):
1071 1002 _ = self.request.translate
1072 1003 c = self.load_default_context()
1073 1004
1074 1005 user_id = self.db_user_id
1075 1006 c.user = self.db_user
1076 1007
1077 1008 email = self.request.POST.get('new_email')
1078 1009 user_data = c.user.get_api_data()
1079 1010 try:
1080 1011
1081 1012 form = UserExtraEmailForm(self.request.translate)()
1082 1013 data = form.to_python({'email': email})
1083 1014 email = data['email']
1084 1015
1085 1016 UserModel().add_extra_email(c.user.user_id, email)
1086 1017 audit_logger.store_web(
1087 1018 'user.edit.email.add',
1088 1019 action_data={'email': email, 'user': user_data},
1089 1020 user=self._rhodecode_user)
1090 1021 Session().commit()
1091 1022 h.flash(_("Added new email address `%s` for user account") % email,
1092 1023 category='success')
1093 1024 except formencode.Invalid as error:
1094 1025 h.flash(h.escape(error.error_dict['email']), category='error')
1095 1026 except IntegrityError:
1096 1027 log.warning("Email %s already exists", email)
1097 1028 h.flash(_('Email `{}` is already registered for another user.').format(email),
1098 1029 category='error')
1099 1030 except Exception:
1100 1031 log.exception("Exception during email saving")
1101 1032 h.flash(_('An error occurred during email saving'),
1102 1033 category='error')
1103 1034 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
1104 1035
1105 1036 @LoginRequired()
1106 1037 @HasPermissionAllDecorator('hg.admin')
1107 1038 @CSRFRequired()
1108 @view_config(
1109 route_name='edit_user_emails_delete', request_method='POST')
1110 1039 def emails_delete(self):
1111 1040 _ = self.request.translate
1112 1041 c = self.load_default_context()
1113 1042
1114 1043 user_id = self.db_user_id
1115 1044 c.user = self.db_user
1116 1045
1117 1046 email_id = self.request.POST.get('del_email_id')
1118 1047 user_model = UserModel()
1119 1048
1120 1049 email = UserEmailMap.query().get(email_id).email
1121 1050 user_data = c.user.get_api_data()
1122 1051 user_model.delete_extra_email(c.user.user_id, email_id)
1123 1052 audit_logger.store_web(
1124 1053 'user.edit.email.delete',
1125 1054 action_data={'email': email, 'user': user_data},
1126 1055 user=self._rhodecode_user)
1127 1056 Session().commit()
1128 1057 h.flash(_("Removed email address from user account"),
1129 1058 category='success')
1130 1059 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
1131 1060
1132 1061 @LoginRequired()
1133 1062 @HasPermissionAllDecorator('hg.admin')
1134 @view_config(
1135 route_name='edit_user_ips', request_method='GET',
1136 renderer='rhodecode:templates/admin/users/user_edit.mako')
1137 1063 def ips(self):
1138 1064 _ = self.request.translate
1139 1065 c = self.load_default_context()
1140 1066 c.user = self.db_user
1141 1067
1142 1068 c.active = 'ips'
1143 1069 c.user_ip_map = UserIpMap.query() \
1144 1070 .filter(UserIpMap.user == c.user).all()
1145 1071
1146 1072 c.inherit_default_ips = c.user.inherit_default_permissions
1147 1073 c.default_user_ip_map = UserIpMap.query() \
1148 1074 .filter(UserIpMap.user == User.get_default_user()).all()
1149 1075
1150 1076 return self._get_template_context(c)
1151 1077
1152 1078 @LoginRequired()
1153 1079 @HasPermissionAllDecorator('hg.admin')
1154 1080 @CSRFRequired()
1155 @view_config(
1156 route_name='edit_user_ips_add', request_method='POST')
1157 1081 # NOTE(marcink): this view is allowed for default users, as we can
1158 1082 # edit their IP white list
1159 1083 def ips_add(self):
1160 1084 _ = self.request.translate
1161 1085 c = self.load_default_context()
1162 1086
1163 1087 user_id = self.db_user_id
1164 1088 c.user = self.db_user
1165 1089
1166 1090 user_model = UserModel()
1167 1091 desc = self.request.POST.get('description')
1168 1092 try:
1169 1093 ip_list = user_model.parse_ip_range(
1170 1094 self.request.POST.get('new_ip'))
1171 1095 except Exception as e:
1172 1096 ip_list = []
1173 1097 log.exception("Exception during ip saving")
1174 1098 h.flash(_('An error occurred during ip saving:%s' % (e,)),
1175 1099 category='error')
1176 1100 added = []
1177 1101 user_data = c.user.get_api_data()
1178 1102 for ip in ip_list:
1179 1103 try:
1180 1104 form = UserExtraIpForm(self.request.translate)()
1181 1105 data = form.to_python({'ip': ip})
1182 1106 ip = data['ip']
1183 1107
1184 1108 user_model.add_extra_ip(c.user.user_id, ip, desc)
1185 1109 audit_logger.store_web(
1186 1110 'user.edit.ip.add',
1187 1111 action_data={'ip': ip, 'user': user_data},
1188 1112 user=self._rhodecode_user)
1189 1113 Session().commit()
1190 1114 added.append(ip)
1191 1115 except formencode.Invalid as error:
1192 1116 msg = error.error_dict['ip']
1193 1117 h.flash(msg, category='error')
1194 1118 except Exception:
1195 1119 log.exception("Exception during ip saving")
1196 1120 h.flash(_('An error occurred during ip saving'),
1197 1121 category='error')
1198 1122 if added:
1199 1123 h.flash(
1200 1124 _("Added ips %s to user whitelist") % (', '.join(ip_list), ),
1201 1125 category='success')
1202 1126 if 'default_user' in self.request.POST:
1203 1127 # case for editing global IP list we do it for 'DEFAULT' user
1204 1128 raise HTTPFound(h.route_path('admin_permissions_ips'))
1205 1129 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1206 1130
1207 1131 @LoginRequired()
1208 1132 @HasPermissionAllDecorator('hg.admin')
1209 1133 @CSRFRequired()
1210 @view_config(
1211 route_name='edit_user_ips_delete', request_method='POST')
1212 1134 # NOTE(marcink): this view is allowed for default users, as we can
1213 1135 # edit their IP white list
1214 1136 def ips_delete(self):
1215 1137 _ = self.request.translate
1216 1138 c = self.load_default_context()
1217 1139
1218 1140 user_id = self.db_user_id
1219 1141 c.user = self.db_user
1220 1142
1221 1143 ip_id = self.request.POST.get('del_ip_id')
1222 1144 user_model = UserModel()
1223 1145 user_data = c.user.get_api_data()
1224 1146 ip = UserIpMap.query().get(ip_id).ip_addr
1225 1147 user_model.delete_extra_ip(c.user.user_id, ip_id)
1226 1148 audit_logger.store_web(
1227 1149 'user.edit.ip.delete', action_data={'ip': ip, 'user': user_data},
1228 1150 user=self._rhodecode_user)
1229 1151 Session().commit()
1230 1152 h.flash(_("Removed ip address from user whitelist"), category='success')
1231 1153
1232 1154 if 'default_user' in self.request.POST:
1233 1155 # case for editing global IP list we do it for 'DEFAULT' user
1234 1156 raise HTTPFound(h.route_path('admin_permissions_ips'))
1235 1157 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1236 1158
1237 1159 @LoginRequired()
1238 1160 @HasPermissionAllDecorator('hg.admin')
1239 @view_config(
1240 route_name='edit_user_groups_management', request_method='GET',
1241 renderer='rhodecode:templates/admin/users/user_edit.mako')
1242 1161 def groups_management(self):
1243 1162 c = self.load_default_context()
1244 1163 c.user = self.db_user
1245 1164 c.data = c.user.group_member
1246 1165
1247 1166 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
1248 1167 for group in c.user.group_member]
1249 1168 c.groups = json.dumps(groups)
1250 1169 c.active = 'groups'
1251 1170
1252 1171 return self._get_template_context(c)
1253 1172
1254 1173 @LoginRequired()
1255 1174 @HasPermissionAllDecorator('hg.admin')
1256 1175 @CSRFRequired()
1257 @view_config(
1258 route_name='edit_user_groups_management_updates', request_method='POST')
1259 1176 def groups_management_updates(self):
1260 1177 _ = self.request.translate
1261 1178 c = self.load_default_context()
1262 1179
1263 1180 user_id = self.db_user_id
1264 1181 c.user = self.db_user
1265 1182
1266 1183 user_groups = set(self.request.POST.getall('users_group_id'))
1267 1184 user_groups_objects = []
1268 1185
1269 1186 for ugid in user_groups:
1270 1187 user_groups_objects.append(
1271 1188 UserGroupModel().get_group(safe_int(ugid)))
1272 1189 user_group_model = UserGroupModel()
1273 1190 added_to_groups, removed_from_groups = \
1274 1191 user_group_model.change_groups(c.user, user_groups_objects)
1275 1192
1276 1193 user_data = c.user.get_api_data()
1277 1194 for user_group_id in added_to_groups:
1278 1195 user_group = UserGroup.get(user_group_id)
1279 1196 old_values = user_group.get_api_data()
1280 1197 audit_logger.store_web(
1281 1198 'user_group.edit.member.add',
1282 1199 action_data={'user': user_data, 'old_data': old_values},
1283 1200 user=self._rhodecode_user)
1284 1201
1285 1202 for user_group_id in removed_from_groups:
1286 1203 user_group = UserGroup.get(user_group_id)
1287 1204 old_values = user_group.get_api_data()
1288 1205 audit_logger.store_web(
1289 1206 'user_group.edit.member.delete',
1290 1207 action_data={'user': user_data, 'old_data': old_values},
1291 1208 user=self._rhodecode_user)
1292 1209
1293 1210 Session().commit()
1294 1211 c.active = 'user_groups_management'
1295 1212 h.flash(_("Groups successfully changed"), category='success')
1296 1213
1297 1214 return HTTPFound(h.route_path(
1298 1215 'edit_user_groups_management', user_id=user_id))
1299 1216
1300 1217 @LoginRequired()
1301 1218 @HasPermissionAllDecorator('hg.admin')
1302 @view_config(
1303 route_name='edit_user_audit_logs', request_method='GET',
1304 renderer='rhodecode:templates/admin/users/user_edit.mako')
1305 1219 def user_audit_logs(self):
1306 1220 _ = self.request.translate
1307 1221 c = self.load_default_context()
1308 1222 c.user = self.db_user
1309 1223
1310 1224 c.active = 'audit'
1311 1225
1312 1226 p = safe_int(self.request.GET.get('page', 1), 1)
1313 1227
1314 1228 filter_term = self.request.GET.get('filter')
1315 1229 user_log = UserModel().get_user_log(c.user, filter_term)
1316 1230
1317 1231 def url_generator(page_num):
1318 1232 query_params = {
1319 1233 'page': page_num
1320 1234 }
1321 1235 if filter_term:
1322 1236 query_params['filter'] = filter_term
1323 1237 return self.request.current_route_path(_query=query_params)
1324 1238
1325 1239 c.audit_logs = SqlPage(
1326 1240 user_log, page=p, items_per_page=10, url_maker=url_generator)
1327 1241 c.filter_term = filter_term
1328 1242 return self._get_template_context(c)
1329 1243
1330 1244 @LoginRequired()
1331 1245 @HasPermissionAllDecorator('hg.admin')
1332 @view_config(
1333 route_name='edit_user_audit_logs_download', request_method='GET',
1334 renderer='string')
1335 1246 def user_audit_logs_download(self):
1336 1247 _ = self.request.translate
1337 1248 c = self.load_default_context()
1338 1249 c.user = self.db_user
1339 1250
1340 1251 user_log = UserModel().get_user_log(c.user, filter_term=None)
1341 1252
1342 1253 audit_log_data = {}
1343 1254 for entry in user_log:
1344 1255 audit_log_data[entry.user_log_id] = entry.get_dict()
1345 1256
1346 1257 response = Response(json.dumps(audit_log_data, indent=4))
1347 1258 response.content_disposition = str(
1348 1259 'attachment; filename=%s' % 'user_{}_audit_logs.json'.format(c.user.user_id))
1349 1260 response.content_type = 'application/json'
1350 1261
1351 1262 return response
1352 1263
1353 1264 @LoginRequired()
1354 1265 @HasPermissionAllDecorator('hg.admin')
1355 @view_config(
1356 route_name='edit_user_perms_summary', request_method='GET',
1357 renderer='rhodecode:templates/admin/users/user_edit.mako')
1358 1266 def user_perms_summary(self):
1359 1267 _ = self.request.translate
1360 1268 c = self.load_default_context()
1361 1269 c.user = self.db_user
1362 1270
1363 1271 c.active = 'perms_summary'
1364 1272 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1365 1273
1366 1274 return self._get_template_context(c)
1367 1275
1368 1276 @LoginRequired()
1369 1277 @HasPermissionAllDecorator('hg.admin')
1370 @view_config(
1371 route_name='edit_user_perms_summary_json', request_method='GET',
1372 renderer='json_ext')
1373 1278 def user_perms_summary_json(self):
1374 1279 self.load_default_context()
1375 1280 perm_user = self.db_user.AuthUser(ip_addr=self.request.remote_addr)
1376 1281
1377 1282 return perm_user.permissions
1378 1283
1379 1284 @LoginRequired()
1380 1285 @HasPermissionAllDecorator('hg.admin')
1381 @view_config(
1382 route_name='edit_user_caches', request_method='GET',
1383 renderer='rhodecode:templates/admin/users/user_edit.mako')
1384 1286 def user_caches(self):
1385 1287 _ = self.request.translate
1386 1288 c = self.load_default_context()
1387 1289 c.user = self.db_user
1388 1290
1389 1291 c.active = 'caches'
1390 1292 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1391 1293
1392 1294 cache_namespace_uid = 'cache_user_auth.{}'.format(self.db_user.user_id)
1393 1295 c.region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
1394 1296 c.backend = c.region.backend
1395 1297 c.user_keys = sorted(c.region.backend.list_keys(prefix=cache_namespace_uid))
1396 1298
1397 1299 return self._get_template_context(c)
1398 1300
1399 1301 @LoginRequired()
1400 1302 @HasPermissionAllDecorator('hg.admin')
1401 1303 @CSRFRequired()
1402 @view_config(
1403 route_name='edit_user_caches_update', request_method='POST')
1404 1304 def user_caches_update(self):
1405 1305 _ = self.request.translate
1406 1306 c = self.load_default_context()
1407 1307 c.user = self.db_user
1408 1308
1409 1309 c.active = 'caches'
1410 1310 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1411 1311
1412 1312 cache_namespace_uid = 'cache_user_auth.{}'.format(self.db_user.user_id)
1413 1313 del_keys = rc_cache.clear_cache_namespace('cache_perms', cache_namespace_uid)
1414 1314
1415 1315 h.flash(_("Deleted {} cache keys").format(del_keys), category='success')
1416 1316
1417 1317 return HTTPFound(h.route_path(
1418 1318 'edit_user_caches', user_id=c.user.user_id))
@@ -1,96 +1,106 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 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
23 23 from pyramid.events import ApplicationCreated
24 24 from pyramid.settings import asbool
25 25
26 26 from rhodecode.apps._base import ADMIN_PREFIX
27 27 from rhodecode.lib.ext_json import json
28 28
29 29
30 30 def url_gen(request):
31 31 registry = request.registry
32 32 longpoll_url = registry.settings.get('channelstream.longpoll_url', '')
33 33 ws_url = registry.settings.get('channelstream.ws_url', '')
34 34 proxy_url = request.route_url('channelstream_proxy')
35 35 urls = {
36 36 'connect': request.route_path('channelstream_connect'),
37 37 'subscribe': request.route_path('channelstream_subscribe'),
38 38 'longpoll': longpoll_url or proxy_url,
39 39 'ws': ws_url or proxy_url.replace('http', 'ws')
40 40 }
41 41 return json.dumps(urls)
42 42
43 43
44 44 PLUGIN_DEFINITION = {
45 45 'name': 'channelstream',
46 46 'config': {
47 47 'javascript': [],
48 48 'css': [],
49 49 'template_hooks': {
50 50 'plugin_init_template': 'rhodecode:templates/channelstream/plugin_init.mako'
51 51 },
52 52 'url_gen': url_gen,
53 53 'static': None,
54 54 'enabled': False,
55 55 'server': '',
56 56 'secret': ''
57 57 }
58 58 }
59 59
60 60
61 61 def maybe_create_history_store(event):
62 62 # create plugin history location
63 63 settings = event.app.registry.settings
64 64 history_dir = settings.get('channelstream.history.location', '')
65 65 if history_dir and not os.path.exists(history_dir):
66 66 os.makedirs(history_dir, 0o750)
67 67
68 68
69 69 def includeme(config):
70 from rhodecode.apps.channelstream.views import ChannelstreamView
71
70 72 settings = config.registry.settings
71 73 PLUGIN_DEFINITION['config']['enabled'] = asbool(
72 74 settings.get('channelstream.enabled'))
73 75 PLUGIN_DEFINITION['config']['server'] = settings.get(
74 76 'channelstream.server', '')
75 77 PLUGIN_DEFINITION['config']['secret'] = settings.get(
76 78 'channelstream.secret', '')
77 79 PLUGIN_DEFINITION['config']['history.location'] = settings.get(
78 80 'channelstream.history.location', '')
79 81 config.register_rhodecode_plugin(
80 82 PLUGIN_DEFINITION['name'],
81 83 PLUGIN_DEFINITION['config']
82 84 )
83 85 config.add_subscriber(maybe_create_history_store, ApplicationCreated)
84 86
85 87 config.add_route(
86 88 name='channelstream_connect',
87 89 pattern=ADMIN_PREFIX + '/channelstream/connect')
90 config.add_view(
91 ChannelstreamView,
92 attr='channelstream_connect',
93 route_name='channelstream_connect', renderer='json_ext')
94
88 95 config.add_route(
89 96 name='channelstream_subscribe',
90 97 pattern=ADMIN_PREFIX + '/channelstream/subscribe')
98 config.add_view(
99 ChannelstreamView,
100 attr='channelstream_subscribe',
101 route_name='channelstream_subscribe', renderer='json_ext')
102
91 103 config.add_route(
92 104 name='channelstream_proxy',
93 105 pattern=settings.get('channelstream.proxy_path') or '/_channelstream')
94 106
95 # Scan module for configuration decorators.
96 config.scan('.views', ignore='.tests')
@@ -1,187 +1,185 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 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 from pyramid.view import view_config
24
25 25 from pyramid.httpexceptions import HTTPBadRequest, HTTPForbidden, HTTPBadGateway
26 26
27 27 from rhodecode.apps._base import BaseAppView
28 28 from rhodecode.lib.channelstream import (
29 29 channelstream_request, get_channelstream_server_url,
30 30 ChannelstreamConnectionException,
31 31 ChannelstreamPermissionException,
32 32 check_channel_permissions,
33 33 get_connection_validators,
34 34 get_user_data,
35 35 parse_channels_info,
36 36 update_history_from_logs,
37 37 USER_STATE_PUBLIC_KEYS)
38 38
39 39 from rhodecode.lib.auth import NotAnonymous
40 40
41 41 log = logging.getLogger(__name__)
42 42
43 43
44 44 class ChannelstreamView(BaseAppView):
45 45
46 46 def load_default_context(self):
47 47 c = self._get_local_tmpl_context()
48 48 self.channelstream_config = \
49 49 self.request.registry.rhodecode_plugins['channelstream']
50 50 if not self.channelstream_config.get('enabled'):
51 51 log.error('Channelstream plugin is disabled')
52 52 raise HTTPBadRequest()
53 53
54 54 return c
55 55
56 56 @NotAnonymous()
57 @view_config(route_name='channelstream_connect', renderer='json_ext')
58 def connect(self):
57 def channelstream_connect(self):
59 58 """ handle authorization of users trying to connect """
60 59
61 60 self.load_default_context()
62 61 try:
63 62 json_body = self.request.json_body
64 63 except Exception:
65 64 log.exception('Failed to decode json from request')
66 65 raise HTTPBadRequest()
67 66
68 67 try:
69 68 channels = check_channel_permissions(
70 69 json_body.get('channels'),
71 70 get_connection_validators(self.request.registry))
72 71 except ChannelstreamPermissionException:
73 72 log.error('Incorrect permissions for requested channels')
74 73 raise HTTPForbidden()
75 74
76 75 user = self._rhodecode_user
77 76 if user.user_id:
78 77 user_data = get_user_data(user.user_id)
79 78 else:
80 79 user_data = {
81 80 'id': None,
82 81 'username': None,
83 82 'first_name': None,
84 83 'last_name': None,
85 84 'icon_link': None,
86 85 'display_name': None,
87 86 'display_link': None,
88 87 }
89 88
90 89 #user_data['permissions'] = self._rhodecode_user.permissions_safe
91 90
92 91 payload = {
93 92 'username': user.username,
94 93 'user_state': user_data,
95 94 'conn_id': str(uuid.uuid4()),
96 95 'channels': channels,
97 96 'channel_configs': {},
98 97 'state_public_keys': USER_STATE_PUBLIC_KEYS,
99 98 'info': {
100 99 'exclude_channels': ['broadcast']
101 100 }
102 101 }
103 102 filtered_channels = [channel for channel in channels
104 103 if channel != 'broadcast']
105 104 for channel in filtered_channels:
106 105 payload['channel_configs'][channel] = {
107 106 'notify_presence': True,
108 107 'history_size': 100,
109 108 'store_history': True,
110 109 'broadcast_presence_with_user_lists': True
111 110 }
112 111 # connect user to server
113 112 channelstream_url = get_channelstream_server_url(
114 113 self.channelstream_config, '/connect')
115 114 try:
116 115 connect_result = channelstream_request(
117 116 self.channelstream_config, payload, '/connect')
118 117 except ChannelstreamConnectionException:
119 118 log.exception(
120 119 'Channelstream service at {} is down'.format(channelstream_url))
121 120 return HTTPBadGateway()
122 121
123 122 channel_info = connect_result.get('channels_info')
124 123 if not channel_info:
125 124 raise HTTPBadRequest()
126 125
127 126 connect_result['channels'] = channels
128 127 connect_result['channels_info'] = parse_channels_info(
129 128 channel_info, include_channel_info=filtered_channels)
130 129 update_history_from_logs(self.channelstream_config,
131 130 filtered_channels, connect_result)
132 131 return connect_result
133 132
134 133 @NotAnonymous()
135 @view_config(route_name='channelstream_subscribe', renderer='json_ext')
136 def subscribe(self):
134 def channelstream_subscribe(self):
137 135 """ can be used to subscribe specific connection to other channels """
138 136 self.load_default_context()
139 137 try:
140 138 json_body = self.request.json_body
141 139 except Exception:
142 140 log.exception('Failed to decode json from request')
143 141 raise HTTPBadRequest()
144 142 try:
145 143 channels = check_channel_permissions(
146 144 json_body.get('channels'),
147 145 get_connection_validators(self.request.registry))
148 146 except ChannelstreamPermissionException:
149 147 log.error('Incorrect permissions for requested channels')
150 148 raise HTTPForbidden()
151 149 payload = {'conn_id': json_body.get('conn_id', ''),
152 150 'channels': channels,
153 151 'channel_configs': {},
154 152 'info': {
155 153 'exclude_channels': ['broadcast']}
156 154 }
157 155 filtered_channels = [chan for chan in channels if chan != 'broadcast']
158 156 for channel in filtered_channels:
159 157 payload['channel_configs'][channel] = {
160 158 'notify_presence': True,
161 159 'history_size': 100,
162 160 'store_history': True,
163 161 'broadcast_presence_with_user_lists': True
164 162 }
165 163
166 164 channelstream_url = get_channelstream_server_url(
167 165 self.channelstream_config, '/subscribe')
168 166 try:
169 167 connect_result = channelstream_request(
170 168 self.channelstream_config, payload, '/subscribe')
171 169 except ChannelstreamConnectionException:
172 170 log.exception(
173 171 'Channelstream service at {} is down'.format(channelstream_url))
174 172 return HTTPBadGateway()
175 173
176 174 channel_info = connect_result.get('channels_info')
177 175 if not channel_info:
178 176 raise HTTPBadRequest()
179 177
180 178 # include_channel_info will limit history only to new channel
181 179 # to not overwrite histories on other channels in client
182 180 connect_result['channels_info'] = parse_channels_info(
183 181 channel_info,
184 182 include_channel_info=filtered_channels)
185 183 update_history_from_logs(
186 184 self.channelstream_config, filtered_channels, connect_result)
187 185 return connect_result
@@ -1,59 +1,81 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2020 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 from rhodecode.apps._base import ADMIN_PREFIX
21 21 from rhodecode.lib.utils2 import str2bool
22 22
23 23
24 24 class DebugStylePredicate(object):
25 25 def __init__(self, val, config):
26 26 self.val = val
27 27
28 28 def text(self):
29 29 return 'debug style route = %s' % self.val
30 30
31 31 phash = text
32 32
33 33 def __call__(self, info, request):
34 34 return str2bool(request.registry.settings.get('debug_style'))
35 35
36 36
37 37 def includeme(config):
38 from rhodecode.apps.debug_style.views import DebugStyleView
39
38 40 config.add_route_predicate(
39 41 'debug_style', DebugStylePredicate)
40 42
41 43 config.add_route(
42 44 name='debug_style_home',
43 45 pattern=ADMIN_PREFIX + '/debug_style',
44 46 debug_style=True)
47 config.add_view(
48 DebugStyleView,
49 attr='index',
50 route_name='debug_style_home', request_method='GET',
51 renderer=None)
52
45 53 config.add_route(
46 54 name='debug_style_email',
47 55 pattern=ADMIN_PREFIX + '/debug_style/email/{email_id}',
48 56 debug_style=True)
57 config.add_view(
58 DebugStyleView,
59 attr='render_email',
60 route_name='debug_style_email', request_method='GET',
61 renderer=None)
62
49 63 config.add_route(
50 64 name='debug_style_email_plain_rendered',
51 65 pattern=ADMIN_PREFIX + '/debug_style/email-rendered/{email_id}',
52 66 debug_style=True)
67 config.add_view(
68 DebugStyleView,
69 attr='render_email',
70 route_name='debug_style_email_plain_rendered', request_method='GET',
71 renderer=None)
72
53 73 config.add_route(
54 74 name='debug_style_template',
55 75 pattern=ADMIN_PREFIX + '/debug_style/t/{t_path}',
56 76 debug_style=True)
57
58 # Scan module for configuration decorators.
59 config.scan('.views', ignore='.tests')
77 config.add_view(
78 DebugStyleView,
79 attr='template',
80 route_name='debug_style_template', request_method='GET',
81 renderer=None)
@@ -1,486 +1,471 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2020 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 import datetime
24 24
25 from pyramid.view import view_config
26 25 from pyramid.renderers import render_to_response
27 26 from rhodecode.apps._base import BaseAppView
28 27 from rhodecode.lib.celerylib import run_task, tasks
29 28 from rhodecode.lib.utils2 import AttributeDict
30 29 from rhodecode.model.db import User
31 30 from rhodecode.model.notification import EmailNotificationModel
32 31
33 32 log = logging.getLogger(__name__)
34 33
35 34
36 35 class DebugStyleView(BaseAppView):
37 36
38 37 def load_default_context(self):
39 38 c = self._get_local_tmpl_context()
40
41 39 return c
42 40
43 @view_config(
44 route_name='debug_style_home', request_method='GET',
45 renderer=None)
46 41 def index(self):
47 42 c = self.load_default_context()
48 43 c.active = 'index'
49 44
50 45 return render_to_response(
51 46 'debug_style/index.html', self._get_template_context(c),
52 47 request=self.request)
53 48
54 @view_config(
55 route_name='debug_style_email', request_method='GET',
56 renderer=None)
57 @view_config(
58 route_name='debug_style_email_plain_rendered', request_method='GET',
59 renderer=None)
60 49 def render_email(self):
61 50 c = self.load_default_context()
62 51 email_id = self.request.matchdict['email_id']
63 52 c.active = 'emails'
64 53
65 54 pr = AttributeDict(
66 55 pull_request_id=123,
67 56 title='digital_ocean: fix redis, elastic search start on boot, '
68 57 'fix fd limits on supervisor, set postgres 11 version',
69 58 description='''
70 59 Check if we should use full-topic or mini-topic.
71 60
72 61 - full topic produces some problems with merge states etc
73 62 - server-mini-topic needs probably tweeks.
74 63 ''',
75 64 repo_name='foobar',
76 65 source_ref_parts=AttributeDict(type='branch', name='fix-ticket-2000'),
77 66 target_ref_parts=AttributeDict(type='branch', name='master'),
78 67 )
79 68
80 69 target_repo = AttributeDict(repo_name='repo_group/target_repo')
81 70 source_repo = AttributeDict(repo_name='repo_group/source_repo')
82 71 user = User.get_by_username(self.request.GET.get('user')) or self._rhodecode_db_user
83 72 # file/commit changes for PR update
84 73 commit_changes = AttributeDict({
85 74 'added': ['aaaaaaabbbbb', 'cccccccddddddd'],
86 75 'removed': ['eeeeeeeeeee'],
87 76 })
88 77
89 78 file_changes = AttributeDict({
90 79 'added': ['a/file1.md', 'file2.py'],
91 80 'modified': ['b/modified_file.rst'],
92 81 'removed': ['.idea'],
93 82 })
94 83
95 84 exc_traceback = {
96 85 'exc_utc_date': '2020-03-26T12:54:50.683281',
97 86 'exc_id': 139638856342656,
98 87 'exc_timestamp': '1585227290.683288',
99 88 'version': 'v1',
100 89 'exc_message': 'Traceback (most recent call last):\n File "/nix/store/s43k2r9rysfbzmsjdqnxgzvvb7zjhkxb-python2.7-pyramid-1.10.4/lib/python2.7/site-packages/pyramid/tweens.py", line 41, in excview_tween\n response = handler(request)\n File "/nix/store/s43k2r9rysfbzmsjdqnxgzvvb7zjhkxb-python2.7-pyramid-1.10.4/lib/python2.7/site-packages/pyramid/router.py", line 148, in handle_request\n registry, request, context, context_iface, view_name\n File "/nix/store/s43k2r9rysfbzmsjdqnxgzvvb7zjhkxb-python2.7-pyramid-1.10.4/lib/python2.7/site-packages/pyramid/view.py", line 667, in _call_view\n response = view_callable(context, request)\n File "/nix/store/s43k2r9rysfbzmsjdqnxgzvvb7zjhkxb-python2.7-pyramid-1.10.4/lib/python2.7/site-packages/pyramid/config/views.py", line 188, in attr_view\n return view(context, request)\n File "/nix/store/s43k2r9rysfbzmsjdqnxgzvvb7zjhkxb-python2.7-pyramid-1.10.4/lib/python2.7/site-packages/pyramid/config/views.py", line 214, in predicate_wrapper\n return view(context, request)\n File "/nix/store/s43k2r9rysfbzmsjdqnxgzvvb7zjhkxb-python2.7-pyramid-1.10.4/lib/python2.7/site-packages/pyramid/viewderivers.py", line 401, in viewresult_to_response\n result = view(context, request)\n File "/nix/store/s43k2r9rysfbzmsjdqnxgzvvb7zjhkxb-python2.7-pyramid-1.10.4/lib/python2.7/site-packages/pyramid/viewderivers.py", line 132, in _class_view\n response = getattr(inst, attr)()\n File "/mnt/hgfs/marcink/workspace/rhodecode-enterprise-ce/rhodecode/apps/debug_style/views.py", line 355, in render_email\n template_type, **email_kwargs.get(email_id, {}))\n File "/mnt/hgfs/marcink/workspace/rhodecode-enterprise-ce/rhodecode/model/notification.py", line 402, in render_email\n body = email_template.render(None, **_kwargs)\n File "/mnt/hgfs/marcink/workspace/rhodecode-enterprise-ce/rhodecode/lib/partial_renderer.py", line 95, in render\n return self._render_with_exc(tmpl, args, kwargs)\n File "/mnt/hgfs/marcink/workspace/rhodecode-enterprise-ce/rhodecode/lib/partial_renderer.py", line 79, in _render_with_exc\n return render_func.render(*args, **kwargs)\n File "/nix/store/dakh34sxz4yfr435c0cwjz0sd6hnd5g3-python2.7-mako-1.1.0/lib/python2.7/site-packages/mako/template.py", line 476, in render\n return runtime._render(self, self.callable_, args, data)\n File "/nix/store/dakh34sxz4yfr435c0cwjz0sd6hnd5g3-python2.7-mako-1.1.0/lib/python2.7/site-packages/mako/runtime.py", line 883, in _render\n **_kwargs_for_callable(callable_, data)\n File "/nix/store/dakh34sxz4yfr435c0cwjz0sd6hnd5g3-python2.7-mako-1.1.0/lib/python2.7/site-packages/mako/runtime.py", line 920, in _render_context\n _exec_template(inherit, lclcontext, args=args, kwargs=kwargs)\n File "/nix/store/dakh34sxz4yfr435c0cwjz0sd6hnd5g3-python2.7-mako-1.1.0/lib/python2.7/site-packages/mako/runtime.py", line 947, in _exec_template\n callable_(context, *args, **kwargs)\n File "rhodecode_templates_email_templates_base_mako", line 63, in render_body\n File "rhodecode_templates_email_templates_exception_tracker_mako", line 43, in render_body\nAttributeError: \'str\' object has no attribute \'get\'\n',
101 90 'exc_type': 'AttributeError'
102 91 }
103 92
104 93 email_kwargs = {
105 94 'test': {},
106 95
107 96 'message': {
108 97 'body': 'message body !'
109 98 },
110 99
111 100 'email_test': {
112 101 'user': user,
113 102 'date': datetime.datetime.now(),
114 103 },
115 104
116 105 'exception': {
117 106 'email_prefix': '[RHODECODE ERROR]',
118 107 'exc_id': exc_traceback['exc_id'],
119 108 'exc_url': 'http://server-url/{}'.format(exc_traceback['exc_id']),
120 109 'exc_type_name': 'NameError',
121 110 'exc_traceback': exc_traceback,
122 111 },
123 112
124 113 'password_reset': {
125 114 'password_reset_url': 'http://example.com/reset-rhodecode-password/token',
126 115
127 116 'user': user,
128 117 'date': datetime.datetime.now(),
129 118 'email': 'test@rhodecode.com',
130 119 'first_admin_email': User.get_first_super_admin().email
131 120 },
132 121
133 122 'password_reset_confirmation': {
134 123 'new_password': 'new-password-example',
135 124 'user': user,
136 125 'date': datetime.datetime.now(),
137 126 'email': 'test@rhodecode.com',
138 127 'first_admin_email': User.get_first_super_admin().email
139 128 },
140 129
141 130 'registration': {
142 131 'user': user,
143 132 'date': datetime.datetime.now(),
144 133 },
145 134
146 135 'pull_request_comment': {
147 136 'user': user,
148 137
149 138 'status_change': None,
150 139 'status_change_type': None,
151 140
152 141 'pull_request': pr,
153 142 'pull_request_commits': [],
154 143
155 144 'pull_request_target_repo': target_repo,
156 145 'pull_request_target_repo_url': 'http://target-repo/url',
157 146
158 147 'pull_request_source_repo': source_repo,
159 148 'pull_request_source_repo_url': 'http://source-repo/url',
160 149
161 150 'pull_request_url': 'http://localhost/pr1',
162 151 'pr_comment_url': 'http://comment-url',
163 152 'pr_comment_reply_url': 'http://comment-url#reply',
164 153
165 154 'comment_file': None,
166 155 'comment_line': None,
167 156 'comment_type': 'note',
168 157 'comment_body': 'This is my comment body. *I like !*',
169 158 'comment_id': 2048,
170 159 'renderer_type': 'markdown',
171 160 'mention': True,
172 161
173 162 },
174 163
175 164 'pull_request_comment+status': {
176 165 'user': user,
177 166
178 167 'status_change': 'approved',
179 168 'status_change_type': 'approved',
180 169
181 170 'pull_request': pr,
182 171 'pull_request_commits': [],
183 172
184 173 'pull_request_target_repo': target_repo,
185 174 'pull_request_target_repo_url': 'http://target-repo/url',
186 175
187 176 'pull_request_source_repo': source_repo,
188 177 'pull_request_source_repo_url': 'http://source-repo/url',
189 178
190 179 'pull_request_url': 'http://localhost/pr1',
191 180 'pr_comment_url': 'http://comment-url',
192 181 'pr_comment_reply_url': 'http://comment-url#reply',
193 182
194 183 'comment_type': 'todo',
195 184 'comment_file': None,
196 185 'comment_line': None,
197 186 'comment_body': '''
198 187 I think something like this would be better
199 188
200 189 ```py
201 190 // markdown renderer
202 191
203 192 def db():
204 193 global connection
205 194 return connection
206 195
207 196 ```
208 197
209 198 ''',
210 199 'comment_id': 2048,
211 200 'renderer_type': 'markdown',
212 201 'mention': True,
213 202
214 203 },
215 204
216 205 'pull_request_comment+file': {
217 206 'user': user,
218 207
219 208 'status_change': None,
220 209 'status_change_type': None,
221 210
222 211 'pull_request': pr,
223 212 'pull_request_commits': [],
224 213
225 214 'pull_request_target_repo': target_repo,
226 215 'pull_request_target_repo_url': 'http://target-repo/url',
227 216
228 217 'pull_request_source_repo': source_repo,
229 218 'pull_request_source_repo_url': 'http://source-repo/url',
230 219
231 220 'pull_request_url': 'http://localhost/pr1',
232 221
233 222 'pr_comment_url': 'http://comment-url',
234 223 'pr_comment_reply_url': 'http://comment-url#reply',
235 224
236 225 'comment_file': 'rhodecode/model/get_flow_commits',
237 226 'comment_line': 'o1210',
238 227 'comment_type': 'todo',
239 228 'comment_body': '''
240 229 I like this !
241 230
242 231 But please check this code
243 232
244 233 .. code-block:: javascript
245 234
246 235 // THIS IS RST CODE
247 236
248 237 this.createResolutionComment = function(commentId) {
249 238 // hide the trigger text
250 239 $('#resolve-comment-{0}'.format(commentId)).hide();
251 240
252 241 var comment = $('#comment-'+commentId);
253 242 var commentData = comment.data();
254 243 if (commentData.commentInline) {
255 244 this.createComment(comment, f_path, line_no, commentId)
256 245 } else {
257 246 Rhodecode.comments.createGeneralComment('general', "$placeholder", commentId)
258 247 }
259 248
260 249 return false;
261 250 };
262 251
263 252 This should work better !
264 253 ''',
265 254 'comment_id': 2048,
266 255 'renderer_type': 'rst',
267 256 'mention': True,
268 257
269 258 },
270 259
271 260 'pull_request_update': {
272 261 'updating_user': user,
273 262
274 263 'status_change': None,
275 264 'status_change_type': None,
276 265
277 266 'pull_request': pr,
278 267 'pull_request_commits': [],
279 268
280 269 'pull_request_target_repo': target_repo,
281 270 'pull_request_target_repo_url': 'http://target-repo/url',
282 271
283 272 'pull_request_source_repo': source_repo,
284 273 'pull_request_source_repo_url': 'http://source-repo/url',
285 274
286 275 'pull_request_url': 'http://localhost/pr1',
287 276
288 277 # update comment links
289 278 'pr_comment_url': 'http://comment-url',
290 279 'pr_comment_reply_url': 'http://comment-url#reply',
291 280 'ancestor_commit_id': 'f39bd443',
292 281 'added_commits': commit_changes.added,
293 282 'removed_commits': commit_changes.removed,
294 283 'changed_files': (file_changes.added + file_changes.modified + file_changes.removed),
295 284 'added_files': file_changes.added,
296 285 'modified_files': file_changes.modified,
297 286 'removed_files': file_changes.removed,
298 287 },
299 288
300 289 'cs_comment': {
301 290 'user': user,
302 291 'commit': AttributeDict(idx=123, raw_id='a'*40, message='Commit message'),
303 292 'status_change': None,
304 293 'status_change_type': None,
305 294
306 295 'commit_target_repo_url': 'http://foo.example.com/#comment1',
307 296 'repo_name': 'test-repo',
308 297 'comment_type': 'note',
309 298 'comment_file': None,
310 299 'comment_line': None,
311 300 'commit_comment_url': 'http://comment-url',
312 301 'commit_comment_reply_url': 'http://comment-url#reply',
313 302 'comment_body': 'This is my comment body. *I like !*',
314 303 'comment_id': 2048,
315 304 'renderer_type': 'markdown',
316 305 'mention': True,
317 306 },
318 307
319 308 'cs_comment+status': {
320 309 'user': user,
321 310 'commit': AttributeDict(idx=123, raw_id='a' * 40, message='Commit message'),
322 311 'status_change': 'approved',
323 312 'status_change_type': 'approved',
324 313
325 314 'commit_target_repo_url': 'http://foo.example.com/#comment1',
326 315 'repo_name': 'test-repo',
327 316 'comment_type': 'note',
328 317 'comment_file': None,
329 318 'comment_line': None,
330 319 'commit_comment_url': 'http://comment-url',
331 320 'commit_comment_reply_url': 'http://comment-url#reply',
332 321 'comment_body': '''
333 322 Hello **world**
334 323
335 324 This is a multiline comment :)
336 325
337 326 - list
338 327 - list2
339 328 ''',
340 329 'comment_id': 2048,
341 330 'renderer_type': 'markdown',
342 331 'mention': True,
343 332 },
344 333
345 334 'cs_comment+file': {
346 335 'user': user,
347 336 'commit': AttributeDict(idx=123, raw_id='a' * 40, message='Commit message'),
348 337 'status_change': None,
349 338 'status_change_type': None,
350 339
351 340 'commit_target_repo_url': 'http://foo.example.com/#comment1',
352 341 'repo_name': 'test-repo',
353 342
354 343 'comment_type': 'note',
355 344 'comment_file': 'test-file.py',
356 345 'comment_line': 'n100',
357 346
358 347 'commit_comment_url': 'http://comment-url',
359 348 'commit_comment_reply_url': 'http://comment-url#reply',
360 349 'comment_body': 'This is my comment body. *I like !*',
361 350 'comment_id': 2048,
362 351 'renderer_type': 'markdown',
363 352 'mention': True,
364 353 },
365 354
366 355 'pull_request': {
367 356 'user': user,
368 357 'pull_request': pr,
369 358 'pull_request_commits': [
370 359 ('472d1df03bf7206e278fcedc6ac92b46b01c4e21', '''\
371 360 my-account: moved email closer to profile as it's similar data just moved outside.
372 361 '''),
373 362 ('cbfa3061b6de2696c7161ed15ba5c6a0045f90a7', '''\
374 363 users: description edit fixes
375 364
376 365 - tests
377 366 - added metatags info
378 367 '''),
379 368 ],
380 369
381 370 'pull_request_target_repo': target_repo,
382 371 'pull_request_target_repo_url': 'http://target-repo/url',
383 372
384 373 'pull_request_source_repo': source_repo,
385 374 'pull_request_source_repo_url': 'http://source-repo/url',
386 375
387 376 'pull_request_url': 'http://code.rhodecode.com/_pull-request/123',
388 377 'user_role': 'reviewer',
389 378 },
390 379
391 380 'pull_request+reviewer_role': {
392 381 'user': user,
393 382 'pull_request': pr,
394 383 'pull_request_commits': [
395 384 ('472d1df03bf7206e278fcedc6ac92b46b01c4e21', '''\
396 385 my-account: moved email closer to profile as it's similar data just moved outside.
397 386 '''),
398 387 ('cbfa3061b6de2696c7161ed15ba5c6a0045f90a7', '''\
399 388 users: description edit fixes
400 389
401 390 - tests
402 391 - added metatags info
403 392 '''),
404 393 ],
405 394
406 395 'pull_request_target_repo': target_repo,
407 396 'pull_request_target_repo_url': 'http://target-repo/url',
408 397
409 398 'pull_request_source_repo': source_repo,
410 399 'pull_request_source_repo_url': 'http://source-repo/url',
411 400
412 401 'pull_request_url': 'http://code.rhodecode.com/_pull-request/123',
413 402 'user_role': 'reviewer',
414 403 },
415 404
416 405 'pull_request+observer_role': {
417 406 'user': user,
418 407 'pull_request': pr,
419 408 'pull_request_commits': [
420 409 ('472d1df03bf7206e278fcedc6ac92b46b01c4e21', '''\
421 410 my-account: moved email closer to profile as it's similar data just moved outside.
422 411 '''),
423 412 ('cbfa3061b6de2696c7161ed15ba5c6a0045f90a7', '''\
424 413 users: description edit fixes
425 414
426 415 - tests
427 416 - added metatags info
428 417 '''),
429 418 ],
430 419
431 420 'pull_request_target_repo': target_repo,
432 421 'pull_request_target_repo_url': 'http://target-repo/url',
433 422
434 423 'pull_request_source_repo': source_repo,
435 424 'pull_request_source_repo_url': 'http://source-repo/url',
436 425
437 426 'pull_request_url': 'http://code.rhodecode.com/_pull-request/123',
438 427 'user_role': 'observer'
439 428 }
440 429 }
441 430
442 431 template_type = email_id.split('+')[0]
443 432 (c.subject, c.email_body, c.email_body_plaintext) = EmailNotificationModel().render_email(
444 433 template_type, **email_kwargs.get(email_id, {}))
445 434
446 435 test_email = self.request.GET.get('email')
447 436 if test_email:
448 437 recipients = [test_email]
449 438 run_task(tasks.send_email, recipients, c.subject,
450 439 c.email_body_plaintext, c.email_body)
451 440
452 441 if self.request.matched_route.name == 'debug_style_email_plain_rendered':
453 442 template = 'debug_style/email_plain_rendered.mako'
454 443 else:
455 444 template = 'debug_style/email.mako'
456 445 return render_to_response(
457 446 template, self._get_template_context(c),
458 447 request=self.request)
459 448
460 @view_config(
461 route_name='debug_style_template', request_method='GET',
462 renderer=None)
463 449 def template(self):
464 450 t_path = self.request.matchdict['t_path']
465 451 c = self.load_default_context()
466 452 c.active = os.path.splitext(t_path)[0]
467 453 c.came_from = ''
468 454 # NOTE(marcink): extend the email types with variations based on data sets
469 455 c.email_types = {
470 456 'cs_comment+file': {},
471 457 'cs_comment+status': {},
472 458
473 459 'pull_request_comment+file': {},
474 460 'pull_request_comment+status': {},
475 461
476 462 'pull_request_update': {},
477 463
478 464 'pull_request+reviewer_role': {},
479 465 'pull_request+observer_role': {},
480 466 }
481 467 c.email_types.update(EmailNotificationModel.email_types)
482 468
483 469 return render_to_response(
484 470 'debug_style/' + t_path, self._get_template_context(c),
485 471 request=self.request)
486
@@ -1,52 +1,65 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2020 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 import os
21 21 from rhodecode.apps.file_store import config_keys
22 22 from rhodecode.config.middleware import _bool_setting, _string_setting
23 23
24 24
25 25 def _sanitize_settings_and_apply_defaults(settings):
26 26 """
27 27 Set defaults, convert to python types and validate settings.
28 28 """
29 29 _bool_setting(settings, config_keys.enabled, 'true')
30 30
31 31 _string_setting(settings, config_keys.backend, 'local')
32 32
33 33 default_store = os.path.join(os.path.dirname(settings['__file__']), 'upload_store')
34 34 _string_setting(settings, config_keys.store_path, default_store)
35 35
36 36
37 37 def includeme(config):
38 from rhodecode.apps.file_store.views import FileStoreView
39
38 40 settings = config.registry.settings
39 41 _sanitize_settings_and_apply_defaults(settings)
40 42
41 43 config.add_route(
42 44 name='upload_file',
43 45 pattern='/_file_store/upload')
46 config.add_view(
47 FileStoreView,
48 attr='upload_file',
49 route_name='upload_file', request_method='POST', renderer='json_ext')
50
44 51 config.add_route(
45 52 name='download_file',
46 53 pattern='/_file_store/download/{fid:.*}')
54 config.add_view(
55 FileStoreView,
56 attr='download_file',
57 route_name='download_file')
58
47 59 config.add_route(
48 60 name='download_file_by_token',
49 61 pattern='/_file_store/token-download/{_auth_token}/{fid:.*}')
50
51 # Scan module for configuration decorators.
52 config.scan('.views', ignore='.tests')
62 config.add_view(
63 FileStoreView,
64 attr='download_file_by_token',
65 route_name='download_file_by_token')
@@ -1,261 +1,269 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2020 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 time
23 23 import errno
24 import shutil
25 24 import hashlib
26 25
27 26 from rhodecode.lib.ext_json import json
28 27 from rhodecode.apps.file_store import utils
29 28 from rhodecode.apps.file_store.extensions import resolve_extensions
30 29 from rhodecode.apps.file_store.exceptions import (
31 30 FileNotAllowedException, FileOverSizeException)
32 31
33 32 METADATA_VER = 'v1'
34 33
35 34
36 35 def safe_make_dirs(dir_path):
37 36 if not os.path.exists(dir_path):
38 37 try:
39 38 os.makedirs(dir_path)
40 39 except OSError as e:
41 40 if e.errno != errno.EEXIST:
42 41 raise
43 42 return
44 43
45 44
46 45 class LocalFileStorage(object):
47 46
48 47 @classmethod
49 48 def apply_counter(cls, counter, filename):
50 49 name_counted = '%d-%s' % (counter, filename)
51 50 return name_counted
52 51
53 52 @classmethod
54 53 def resolve_name(cls, name, directory):
55 54 """
56 55 Resolves a unique name and the correct path. If a filename
57 56 for that path already exists then a numeric prefix with values > 0 will be
58 57 added, for example test.jpg -> 1-test.jpg etc. initially file would have 0 prefix.
59 58
60 59 :param name: base name of file
61 60 :param directory: absolute directory path
62 61 """
63 62
64 63 counter = 0
65 64 while True:
66 65 name_counted = cls.apply_counter(counter, name)
67 66
68 67 # sub_store prefix to optimize disk usage, e.g some_path/ab/final_file
69 68 sub_store = cls._sub_store_from_filename(name_counted)
70 69 sub_store_path = os.path.join(directory, sub_store)
71 70 safe_make_dirs(sub_store_path)
72 71
73 72 path = os.path.join(sub_store_path, name_counted)
74 73 if not os.path.exists(path):
75 74 return name_counted, path
76 75 counter += 1
77 76
78 77 @classmethod
79 78 def _sub_store_from_filename(cls, filename):
80 79 return filename[:2]
81 80
82 81 @classmethod
83 82 def calculate_path_hash(cls, file_path):
84 83 """
85 84 Efficient calculation of file_path sha256 sum
86 85
87 86 :param file_path:
88 87 :return: sha256sum
89 88 """
90 89 digest = hashlib.sha256()
91 90 with open(file_path, 'rb') as f:
92 91 for chunk in iter(lambda: f.read(1024 * 100), b""):
93 92 digest.update(chunk)
94 93
95 94 return digest.hexdigest()
96 95
97 96 def __init__(self, base_path, extension_groups=None):
98 97
99 98 """
100 99 Local file storage
101 100
102 101 :param base_path: the absolute base path where uploads are stored
103 102 :param extension_groups: extensions string
104 103 """
105 104
106 105 extension_groups = extension_groups or ['any']
107 106 self.base_path = base_path
108 107 self.extensions = resolve_extensions([], groups=extension_groups)
109 108
110 109 def __repr__(self):
111 110 return '{}@{}'.format(self.__class__, self.base_path)
112 111
113 112 def store_path(self, filename):
114 113 """
115 114 Returns absolute file path of the filename, joined to the
116 115 base_path.
117 116
118 117 :param filename: base name of file
119 118 """
120 119 prefix_dir = ''
121 120 if '/' in filename:
122 121 prefix_dir, filename = filename.split('/')
123 122 sub_store = self._sub_store_from_filename(filename)
124 123 else:
125 124 sub_store = self._sub_store_from_filename(filename)
126 125 return os.path.join(self.base_path, prefix_dir, sub_store, filename)
127 126
128 127 def delete(self, filename):
129 128 """
130 129 Deletes the filename. Filename is resolved with the
131 130 absolute path based on base_path. If file does not exist,
132 131 returns **False**, otherwise **True**
133 132
134 133 :param filename: base name of file
135 134 """
136 135 if self.exists(filename):
137 136 os.remove(self.store_path(filename))
138 137 return True
139 138 return False
140 139
141 140 def exists(self, filename):
142 141 """
143 142 Checks if file exists. Resolves filename's absolute
144 143 path based on base_path.
145 144
146 145 :param filename: file_uid name of file, e.g 0-f62b2b2d-9708-4079-a071-ec3f958448d4.svg
147 146 """
148 147 return os.path.exists(self.store_path(filename))
149 148
150 149 def filename_allowed(self, filename, extensions=None):
151 150 """Checks if a filename has an allowed extension
152 151
153 152 :param filename: base name of file
154 153 :param extensions: iterable of extensions (or self.extensions)
155 154 """
156 155 _, ext = os.path.splitext(filename)
157 156 return self.extension_allowed(ext, extensions)
158 157
159 158 def extension_allowed(self, ext, extensions=None):
160 159 """
161 160 Checks if an extension is permitted. Both e.g. ".jpg" and
162 161 "jpg" can be passed in. Extension lookup is case-insensitive.
163 162
164 163 :param ext: extension to check
165 164 :param extensions: iterable of extensions to validate against (or self.extensions)
166 165 """
167 166 def normalize_ext(_ext):
168 167 if _ext.startswith('.'):
169 168 _ext = _ext[1:]
170 169 return _ext.lower()
171 170
172 171 extensions = extensions or self.extensions
173 172 if not extensions:
174 173 return True
175 174
176 175 ext = normalize_ext(ext)
177 176
178 177 return ext in [normalize_ext(x) for x in extensions]
179 178
180 179 def save_file(self, file_obj, filename, directory=None, extensions=None,
181 180 extra_metadata=None, max_filesize=None, randomized_name=True, **kwargs):
182 181 """
183 182 Saves a file object to the uploads location.
184 183 Returns the resolved filename, i.e. the directory +
185 184 the (randomized/incremented) base name.
186 185
187 186 :param file_obj: **cgi.FieldStorage** object (or similar)
188 187 :param filename: original filename
189 188 :param directory: relative path of sub-directory
190 189 :param extensions: iterable of allowed extensions, if not default
191 190 :param max_filesize: maximum size of file that should be allowed
192 191 :param randomized_name: generate random generated UID or fixed based on the filename
193 192 :param extra_metadata: extra JSON metadata to store next to the file with .meta suffix
194 193
195 194 """
196 195
197 196 extensions = extensions or self.extensions
198 197
199 198 if not self.filename_allowed(filename, extensions):
200 199 raise FileNotAllowedException()
201 200
202 201 if directory:
203 202 dest_directory = os.path.join(self.base_path, directory)
204 203 else:
205 204 dest_directory = self.base_path
206 205
207 206 safe_make_dirs(dest_directory)
208 207
209 208 uid_filename = utils.uid_filename(filename, randomized=randomized_name)
210 209
211 210 # resolve also produces special sub-dir for file optimized store
212 211 filename, path = self.resolve_name(uid_filename, dest_directory)
213 212 stored_file_dir = os.path.dirname(path)
214 213
215 file_obj.seek(0)
214 no_body_seek = kwargs.pop('no_body_seek', False)
215 if no_body_seek:
216 pass
217 else:
218 file_obj.seek(0)
216 219
217 220 with open(path, "wb") as dest:
218 shutil.copyfileobj(file_obj, dest)
221 length = 256 * 1024
222 while 1:
223 buf = file_obj.read(length)
224 if not buf:
225 break
226 dest.write(buf)
219 227
220 228 metadata = {}
221 229 if extra_metadata:
222 230 metadata = extra_metadata
223 231
224 232 size = os.stat(path).st_size
225 233
226 234 if max_filesize and size > max_filesize:
227 235 # free up the copied file, and raise exc
228 236 os.remove(path)
229 237 raise FileOverSizeException()
230 238
231 239 file_hash = self.calculate_path_hash(path)
232 240
233 241 metadata.update({
234 242 "filename": filename,
235 243 "size": size,
236 244 "time": time.time(),
237 245 "sha256": file_hash,
238 246 "meta_ver": METADATA_VER
239 247 })
240 248
241 249 filename_meta = filename + '.meta'
242 250 with open(os.path.join(stored_file_dir, filename_meta), "wb") as dest_meta:
243 251 dest_meta.write(json.dumps(metadata))
244 252
245 253 if directory:
246 254 filename = os.path.join(directory, filename)
247 255
248 256 return filename, metadata
249 257
250 258 def get_metadata(self, filename):
251 259 """
252 260 Reads JSON stored metadata for a file
253 261
254 262 :param filename:
255 263 :return:
256 264 """
257 265 filename = self.store_path(filename)
258 266 filename_meta = filename + '.meta'
259 267
260 268 with open(filename_meta, "rb") as source_meta:
261 269 return json.loads(source_meta.read())
@@ -1,195 +1,202 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2020 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 import logging
21 21
22 from pyramid.view import view_config
22
23 23 from pyramid.response import FileResponse
24 24 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
25 25
26 26 from rhodecode.apps._base import BaseAppView
27 27 from rhodecode.apps.file_store import utils
28 28 from rhodecode.apps.file_store.exceptions import (
29 29 FileNotAllowedException, FileOverSizeException)
30 30
31 31 from rhodecode.lib import helpers as h
32 32 from rhodecode.lib import audit_logger
33 33 from rhodecode.lib.auth import (
34 34 CSRFRequired, NotAnonymous, HasRepoPermissionAny, HasRepoGroupPermissionAny,
35 35 LoginRequired)
36 36 from rhodecode.lib.vcs.conf.mtypes import get_mimetypes_db
37 37 from rhodecode.model.db import Session, FileStore, UserApiKeys
38 38
39 39 log = logging.getLogger(__name__)
40 40
41 41
42 42 class FileStoreView(BaseAppView):
43 43 upload_key = 'store_file'
44 44
45 45 def load_default_context(self):
46 46 c = self._get_local_tmpl_context()
47 47 self.storage = utils.get_file_storage(self.request.registry.settings)
48 48 return c
49 49
50 50 def _guess_type(self, file_name):
51 51 """
52 52 Our own type guesser for mimetypes using the rich DB
53 53 """
54 54 if not hasattr(self, 'db'):
55 55 self.db = get_mimetypes_db()
56 56 _content_type, _encoding = self.db.guess_type(file_name, strict=False)
57 57 return _content_type, _encoding
58 58
59 59 def _serve_file(self, file_uid):
60
61 60 if not self.storage.exists(file_uid):
62 61 store_path = self.storage.store_path(file_uid)
63 62 log.debug('File with FID:%s not found in the store under `%s`',
64 63 file_uid, store_path)
65 64 raise HTTPNotFound()
66 65
67 66 db_obj = FileStore.get_by_store_uid(file_uid, safe=True)
68 67 if not db_obj:
69 68 raise HTTPNotFound()
70 69
71 70 # private upload for user
72 71 if db_obj.check_acl and db_obj.scope_user_id:
73 72 log.debug('Artifact: checking scope access for bound artifact user: `%s`',
74 73 db_obj.scope_user_id)
75 74 user = db_obj.user
76 75 if self._rhodecode_db_user.user_id != user.user_id:
77 76 log.warning('Access to file store object forbidden')
78 77 raise HTTPNotFound()
79 78
80 79 # scoped to repository permissions
81 80 if db_obj.check_acl and db_obj.scope_repo_id:
82 81 log.debug('Artifact: checking scope access for bound artifact repo: `%s`',
83 82 db_obj.scope_repo_id)
84 83 repo = db_obj.repo
85 84 perm_set = ['repository.read', 'repository.write', 'repository.admin']
86 85 has_perm = HasRepoPermissionAny(*perm_set)(repo.repo_name, 'FileStore check')
87 86 if not has_perm:
88 87 log.warning('Access to file store object `%s` forbidden', file_uid)
89 88 raise HTTPNotFound()
90 89
91 90 # scoped to repository group permissions
92 91 if db_obj.check_acl and db_obj.scope_repo_group_id:
93 92 log.debug('Artifact: checking scope access for bound artifact repo group: `%s`',
94 93 db_obj.scope_repo_group_id)
95 94 repo_group = db_obj.repo_group
96 95 perm_set = ['group.read', 'group.write', 'group.admin']
97 96 has_perm = HasRepoGroupPermissionAny(*perm_set)(repo_group.group_name, 'FileStore check')
98 97 if not has_perm:
99 98 log.warning('Access to file store object `%s` forbidden', file_uid)
100 99 raise HTTPNotFound()
101 100
102 101 FileStore.bump_access_counter(file_uid)
103 102
104 103 file_path = self.storage.store_path(file_uid)
105 104 content_type = 'application/octet-stream'
106 105 content_encoding = None
107 106
108 107 _content_type, _encoding = self._guess_type(file_path)
109 108 if _content_type:
110 109 content_type = _content_type
111 110
112 111 # For file store we don't submit any session data, this logic tells the
113 112 # Session lib to skip it
114 113 setattr(self.request, '_file_response', True)
115 return FileResponse(file_path, request=self.request,
116 content_type=content_type, content_encoding=content_encoding)
114 response = FileResponse(
115 file_path, request=self.request,
116 content_type=content_type, content_encoding=content_encoding)
117
118 file_name = db_obj.file_display_name
119
120 response.headers["Content-Disposition"] = (
121 'attachment; filename="{}"'.format(str(file_name))
122 )
123 response.headers["X-RC-Artifact-Id"] = str(db_obj.file_store_id)
124 response.headers["X-RC-Artifact-Desc"] = str(db_obj.file_description)
125 response.headers["X-RC-Artifact-Sha256"] = str(db_obj.file_hash)
126 return response
117 127
118 128 @LoginRequired()
119 129 @NotAnonymous()
120 130 @CSRFRequired()
121 @view_config(route_name='upload_file', request_method='POST', renderer='json_ext')
122 131 def upload_file(self):
123 132 self.load_default_context()
124 133 file_obj = self.request.POST.get(self.upload_key)
125 134
126 135 if file_obj is None:
127 136 return {'store_fid': None,
128 137 'access_path': None,
129 138 'error': '{} data field is missing'.format(self.upload_key)}
130 139
131 140 if not hasattr(file_obj, 'filename'):
132 141 return {'store_fid': None,
133 142 'access_path': None,
134 143 'error': 'filename cannot be read from the data field'}
135 144
136 145 filename = file_obj.filename
137 146
138 147 metadata = {
139 148 'user_uploaded': {'username': self._rhodecode_user.username,
140 149 'user_id': self._rhodecode_user.user_id,
141 150 'ip': self._rhodecode_user.ip_addr}}
142 151 try:
143 152 store_uid, metadata = self.storage.save_file(
144 153 file_obj.file, filename, extra_metadata=metadata)
145 154 except FileNotAllowedException:
146 155 return {'store_fid': None,
147 156 'access_path': None,
148 157 'error': 'File {} is not allowed.'.format(filename)}
149 158
150 159 except FileOverSizeException:
151 160 return {'store_fid': None,
152 161 'access_path': None,
153 162 'error': 'File {} is exceeding allowed limit.'.format(filename)}
154 163
155 164 try:
156 165 entry = FileStore.create(
157 166 file_uid=store_uid, filename=metadata["filename"],
158 167 file_hash=metadata["sha256"], file_size=metadata["size"],
159 168 file_description=u'upload attachment',
160 169 check_acl=False, user_id=self._rhodecode_user.user_id
161 170 )
162 171 Session().add(entry)
163 172 Session().commit()
164 173 log.debug('Stored upload in DB as %s', entry)
165 174 except Exception:
166 175 log.exception('Failed to store file %s', filename)
167 176 return {'store_fid': None,
168 177 'access_path': None,
169 178 'error': 'File {} failed to store in DB.'.format(filename)}
170 179
171 180 return {'store_fid': store_uid,
172 181 'access_path': h.route_path('download_file', fid=store_uid)}
173 182
174 183 # ACL is checked by scopes, if no scope the file is accessible to all
175 @view_config(route_name='download_file')
176 184 def download_file(self):
177 185 self.load_default_context()
178 186 file_uid = self.request.matchdict['fid']
179 187 log.debug('Requesting FID:%s from store %s', file_uid, self.storage)
180 188 return self._serve_file(file_uid)
181 189
182 190 # in addition to @LoginRequired ACL is checked by scopes
183 191 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_ARTIFACT_DOWNLOAD])
184 192 @NotAnonymous()
185 @view_config(route_name='download_file_by_token')
186 193 def download_file_by_token(self):
187 194 """
188 195 Special view that allows to access the download file by special URL that
189 196 is stored inside the URL.
190 197
191 198 http://example.com/_file_store/token-download/TOKEN/FILE_UID
192 199 """
193 200 self.load_default_context()
194 201 file_uid = self.request.matchdict['fid']
195 202 return self._serve_file(file_uid)
@@ -1,62 +1,120 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2020 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 from rhodecode.apps._base import ADMIN_PREFIX
21 21
22 22
23 23 def admin_routes(config):
24 from rhodecode.apps.gist.views import GistView
25
24 26 config.add_route(
25 27 name='gists_show', pattern='/gists')
28 config.add_view(
29 GistView,
30 attr='gist_show_all',
31 route_name='gists_show', request_method='GET',
32 renderer='rhodecode:templates/admin/gists/gist_index.mako')
33
26 34 config.add_route(
27 35 name='gists_new', pattern='/gists/new')
36 config.add_view(
37 GistView,
38 attr='gist_new',
39 route_name='gists_new', request_method='GET',
40 renderer='rhodecode:templates/admin/gists/gist_new.mako')
41
28 42 config.add_route(
29 43 name='gists_create', pattern='/gists/create')
44 config.add_view(
45 GistView,
46 attr='gist_create',
47 route_name='gists_create', request_method='POST',
48 renderer='rhodecode:templates/admin/gists/gist_new.mako')
30 49
31 50 config.add_route(
32 51 name='gist_show', pattern='/gists/{gist_id}')
52 config.add_view(
53 GistView,
54 attr='gist_show',
55 route_name='gist_show', request_method='GET',
56 renderer='rhodecode:templates/admin/gists/gist_show.mako')
57
58 config.add_route(
59 name='gist_show_rev',
60 pattern='/gists/{gist_id}/rev/{revision}')
61
62 config.add_view(
63 GistView,
64 attr='gist_show',
65 route_name='gist_show_rev', request_method='GET',
66 renderer='rhodecode:templates/admin/gists/gist_show.mako')
67
68 config.add_route(
69 name='gist_show_formatted',
70 pattern='/gists/{gist_id}/rev/{revision}/{format}')
71 config.add_view(
72 GistView,
73 attr='gist_show',
74 route_name='gist_show_formatted', request_method='GET',
75 renderer=None)
76
77 config.add_route(
78 name='gist_show_formatted_path',
79 pattern='/gists/{gist_id}/rev/{revision}/{format}/{f_path:.*}')
80 config.add_view(
81 GistView,
82 attr='gist_show',
83 route_name='gist_show_formatted_path', request_method='GET',
84 renderer=None)
33 85
34 86 config.add_route(
35 87 name='gist_delete', pattern='/gists/{gist_id}/delete')
88 config.add_view(
89 GistView,
90 attr='gist_delete',
91 route_name='gist_delete', request_method='POST')
36 92
37 93 config.add_route(
38 94 name='gist_edit', pattern='/gists/{gist_id}/edit')
95 config.add_view(
96 GistView,
97 attr='gist_edit',
98 route_name='gist_edit', request_method='GET',
99 renderer='rhodecode:templates/admin/gists/gist_edit.mako')
100
101 config.add_route(
102 name='gist_update', pattern='/gists/{gist_id}/update')
103 config.add_view(
104 GistView,
105 attr='gist_update',
106 route_name='gist_update', request_method='POST',
107 renderer='rhodecode:templates/admin/gists/gist_edit.mako')
39 108
40 109 config.add_route(
41 110 name='gist_edit_check_revision',
42 111 pattern='/gists/{gist_id}/edit/check_revision')
43
44 config.add_route(
45 name='gist_update', pattern='/gists/{gist_id}/update')
46
47 config.add_route(
48 name='gist_show_rev',
49 pattern='/gists/{gist_id}/{revision}')
50 config.add_route(
51 name='gist_show_formatted',
52 pattern='/gists/{gist_id}/{revision}/{format}')
53
54 config.add_route(
55 name='gist_show_formatted_path',
56 pattern='/gists/{gist_id}/{revision}/{format}/{f_path:.*}')
112 config.add_view(
113 GistView,
114 attr='gist_edit_check_revision',
115 route_name='gist_edit_check_revision', request_method='GET',
116 renderer='json_ext')
57 117
58 118
59 119 def includeme(config):
60 120 config.include(admin_routes, route_prefix=ADMIN_PREFIX)
61 # Scan module for configuration decorators.
62 config.scan('.views', ignore='.tests')
@@ -1,391 +1,391 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 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 from rhodecode.lib import helpers as h
25 25 from rhodecode.model.db import User, Gist
26 26 from rhodecode.model.gist import GistModel
27 27 from rhodecode.model.meta import Session
28 28 from rhodecode.tests import (
29 29 TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
30 30 TestController, assert_session_flash)
31 31
32 32
33 33 def route_path(name, params=None, **kwargs):
34 34 import urllib
35 35 from rhodecode.apps._base import ADMIN_PREFIX
36 36
37 37 base_url = {
38 38 'gists_show': ADMIN_PREFIX + '/gists',
39 39 'gists_new': ADMIN_PREFIX + '/gists/new',
40 40 'gists_create': ADMIN_PREFIX + '/gists/create',
41 41 'gist_show': ADMIN_PREFIX + '/gists/{gist_id}',
42 42 'gist_delete': ADMIN_PREFIX + '/gists/{gist_id}/delete',
43 43 'gist_edit': ADMIN_PREFIX + '/gists/{gist_id}/edit',
44 44 'gist_edit_check_revision': ADMIN_PREFIX + '/gists/{gist_id}/edit/check_revision',
45 45 'gist_update': ADMIN_PREFIX + '/gists/{gist_id}/update',
46 'gist_show_rev': ADMIN_PREFIX + '/gists/{gist_id}/{revision}',
47 'gist_show_formatted': ADMIN_PREFIX + '/gists/{gist_id}/{revision}/{format}',
48 'gist_show_formatted_path': ADMIN_PREFIX + '/gists/{gist_id}/{revision}/{format}/{f_path}',
46 'gist_show_rev': ADMIN_PREFIX + '/gists/{gist_id}/rev/{revision}',
47 'gist_show_formatted': ADMIN_PREFIX + '/gists/{gist_id}/rev/{revision}/{format}',
48 'gist_show_formatted_path': ADMIN_PREFIX + '/gists/{gist_id}/rev/{revision}/{format}/{f_path}',
49 49
50 50 }[name].format(**kwargs)
51 51
52 52 if params:
53 53 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
54 54 return base_url
55 55
56 56
57 57 class GistUtility(object):
58 58
59 59 def __init__(self):
60 60 self._gist_ids = []
61 61
62 62 def __call__(
63 63 self, f_name, content='some gist', lifetime=-1,
64 64 description='gist-desc', gist_type='public',
65 65 acl_level=Gist.GIST_PUBLIC, owner=TEST_USER_ADMIN_LOGIN):
66 66 gist_mapping = {
67 67 f_name: {'content': content}
68 68 }
69 69 user = User.get_by_username(owner)
70 70 gist = GistModel().create(
71 71 description, owner=user, gist_mapping=gist_mapping,
72 72 gist_type=gist_type, lifetime=lifetime, gist_acl_level=acl_level)
73 73 Session().commit()
74 74 self._gist_ids.append(gist.gist_id)
75 75 return gist
76 76
77 77 def cleanup(self):
78 78 for gist_id in self._gist_ids:
79 79 gist = Gist.get(gist_id)
80 80 if gist:
81 81 Session().delete(gist)
82 82
83 83 Session().commit()
84 84
85 85
86 86 @pytest.fixture()
87 87 def create_gist(request):
88 88 gist_utility = GistUtility()
89 89 request.addfinalizer(gist_utility.cleanup)
90 90 return gist_utility
91 91
92 92
93 93 class TestGistsController(TestController):
94 94
95 95 def test_index_empty(self, create_gist):
96 96 self.log_user()
97 97 response = self.app.get(route_path('gists_show'))
98 98 response.mustcontain('data: [],')
99 99
100 100 def test_index(self, create_gist):
101 101 self.log_user()
102 102 g1 = create_gist('gist1')
103 103 g2 = create_gist('gist2', lifetime=1400)
104 104 g3 = create_gist('gist3', description='gist3-desc')
105 105 g4 = create_gist('gist4', gist_type='private').gist_access_id
106 106 response = self.app.get(route_path('gists_show'))
107 107
108 108 response.mustcontain(g1.gist_access_id)
109 109 response.mustcontain(g2.gist_access_id)
110 110 response.mustcontain(g3.gist_access_id)
111 111 response.mustcontain('gist3-desc')
112 112 response.mustcontain(no=[g4])
113 113
114 114 # Expiration information should be visible
115 115 expires_tag = '%s' % h.age_component(
116 116 h.time_to_utcdatetime(g2.gist_expires))
117 117 response.mustcontain(expires_tag.replace('"', '\\"'))
118 118
119 119 def test_index_private_gists(self, create_gist):
120 120 self.log_user()
121 121 gist = create_gist('gist5', gist_type='private')
122 122 response = self.app.get(route_path('gists_show', params=dict(private=1)))
123 123
124 124 # and privates
125 125 response.mustcontain(gist.gist_access_id)
126 126
127 127 def test_index_show_all(self, create_gist):
128 128 self.log_user()
129 129 create_gist('gist1')
130 130 create_gist('gist2', lifetime=1400)
131 131 create_gist('gist3', description='gist3-desc')
132 132 create_gist('gist4', gist_type='private')
133 133
134 134 response = self.app.get(route_path('gists_show', params=dict(all=1)))
135 135
136 136 assert len(GistModel.get_all()) == 4
137 137 # and privates
138 138 for gist in GistModel.get_all():
139 139 response.mustcontain(gist.gist_access_id)
140 140
141 141 def test_index_show_all_hidden_from_regular(self, create_gist):
142 142 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
143 143 create_gist('gist2', gist_type='private')
144 144 create_gist('gist3', gist_type='private')
145 145 create_gist('gist4', gist_type='private')
146 146
147 147 response = self.app.get(route_path('gists_show', params=dict(all=1)))
148 148
149 149 assert len(GistModel.get_all()) == 3
150 150 # since we don't have access to private in this view, we
151 151 # should see nothing
152 152 for gist in GistModel.get_all():
153 153 response.mustcontain(no=[gist.gist_access_id])
154 154
155 155 def test_create(self):
156 156 self.log_user()
157 157 response = self.app.post(
158 158 route_path('gists_create'),
159 159 params={'lifetime': -1,
160 160 'content': 'gist test',
161 161 'filename': 'foo',
162 162 'gist_type': 'public',
163 163 'gist_acl_level': Gist.ACL_LEVEL_PUBLIC,
164 164 'csrf_token': self.csrf_token},
165 165 status=302)
166 166 response = response.follow()
167 167 response.mustcontain('added file: foo')
168 168 response.mustcontain('gist test')
169 169
170 170 def test_create_with_path_with_dirs(self):
171 171 self.log_user()
172 172 response = self.app.post(
173 173 route_path('gists_create'),
174 174 params={'lifetime': -1,
175 175 'content': 'gist test',
176 176 'filename': '/home/foo',
177 177 'gist_type': 'public',
178 178 'gist_acl_level': Gist.ACL_LEVEL_PUBLIC,
179 179 'csrf_token': self.csrf_token},
180 180 status=200)
181 181 response.mustcontain('Filename /home/foo cannot be inside a directory')
182 182
183 183 def test_access_expired_gist(self, create_gist):
184 184 self.log_user()
185 185 gist = create_gist('never-see-me')
186 186 gist.gist_expires = 0 # 1970
187 187 Session().add(gist)
188 188 Session().commit()
189 189
190 190 self.app.get(route_path('gist_show', gist_id=gist.gist_access_id),
191 191 status=404)
192 192
193 193 def test_create_private(self):
194 194 self.log_user()
195 195 response = self.app.post(
196 196 route_path('gists_create'),
197 197 params={'lifetime': -1,
198 198 'content': 'private gist test',
199 199 'filename': 'private-foo',
200 200 'gist_type': 'private',
201 201 'gist_acl_level': Gist.ACL_LEVEL_PUBLIC,
202 202 'csrf_token': self.csrf_token},
203 203 status=302)
204 204 response = response.follow()
205 205 response.mustcontain('added file: private-foo<')
206 206 response.mustcontain('private gist test')
207 207 response.mustcontain('Private Gist')
208 208 # Make sure private gists are not indexed by robots
209 209 response.mustcontain(
210 210 '<meta name="robots" content="noindex, nofollow">')
211 211
212 212 def test_create_private_acl_private(self):
213 213 self.log_user()
214 214 response = self.app.post(
215 215 route_path('gists_create'),
216 216 params={'lifetime': -1,
217 217 'content': 'private gist test',
218 218 'filename': 'private-foo',
219 219 'gist_type': 'private',
220 220 'gist_acl_level': Gist.ACL_LEVEL_PRIVATE,
221 221 'csrf_token': self.csrf_token},
222 222 status=302)
223 223 response = response.follow()
224 224 response.mustcontain('added file: private-foo<')
225 225 response.mustcontain('private gist test')
226 226 response.mustcontain('Private Gist')
227 227 # Make sure private gists are not indexed by robots
228 228 response.mustcontain(
229 229 '<meta name="robots" content="noindex, nofollow">')
230 230
231 231 def test_create_with_description(self):
232 232 self.log_user()
233 233 response = self.app.post(
234 234 route_path('gists_create'),
235 235 params={'lifetime': -1,
236 236 'content': 'gist test',
237 237 'filename': 'foo-desc',
238 238 'description': 'gist-desc',
239 239 'gist_type': 'public',
240 240 'gist_acl_level': Gist.ACL_LEVEL_PUBLIC,
241 241 'csrf_token': self.csrf_token},
242 242 status=302)
243 243 response = response.follow()
244 244 response.mustcontain('added file: foo-desc')
245 245 response.mustcontain('gist test')
246 246 response.mustcontain('gist-desc')
247 247
248 248 def test_create_public_with_anonymous_access(self):
249 249 self.log_user()
250 250 params = {
251 251 'lifetime': -1,
252 252 'content': 'gist test',
253 253 'filename': 'foo-desc',
254 254 'description': 'gist-desc',
255 255 'gist_type': 'public',
256 256 'gist_acl_level': Gist.ACL_LEVEL_PUBLIC,
257 257 'csrf_token': self.csrf_token
258 258 }
259 259 response = self.app.post(
260 260 route_path('gists_create'), params=params, status=302)
261 261 self.logout_user()
262 262 response = response.follow()
263 263 response.mustcontain('added file: foo-desc')
264 264 response.mustcontain('gist test')
265 265 response.mustcontain('gist-desc')
266 266
267 267 def test_new(self):
268 268 self.log_user()
269 269 self.app.get(route_path('gists_new'))
270 270
271 271 def test_delete(self, create_gist):
272 272 self.log_user()
273 273 gist = create_gist('delete-me')
274 274 response = self.app.post(
275 275 route_path('gist_delete', gist_id=gist.gist_id),
276 276 params={'csrf_token': self.csrf_token})
277 277 assert_session_flash(response, 'Deleted gist %s' % gist.gist_id)
278 278
279 279 def test_delete_normal_user_his_gist(self, create_gist):
280 280 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
281 281 gist = create_gist('delete-me', owner=TEST_USER_REGULAR_LOGIN)
282 282
283 283 response = self.app.post(
284 284 route_path('gist_delete', gist_id=gist.gist_id),
285 285 params={'csrf_token': self.csrf_token})
286 286 assert_session_flash(response, 'Deleted gist %s' % gist.gist_id)
287 287
288 288 def test_delete_normal_user_not_his_own_gist(self, create_gist):
289 289 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
290 290 gist = create_gist('delete-me-2')
291 291
292 292 self.app.post(
293 293 route_path('gist_delete', gist_id=gist.gist_id),
294 294 params={'csrf_token': self.csrf_token}, status=404)
295 295
296 296 def test_show(self, create_gist):
297 297 gist = create_gist('gist-show-me')
298 298 response = self.app.get(route_path('gist_show', gist_id=gist.gist_access_id))
299 299
300 300 response.mustcontain('added file: gist-show-me<')
301 301
302 302 assert_response = response.assert_response()
303 303 assert_response.element_equals_to(
304 304 'div.rc-user span.user',
305 305 '<a href="/_profiles/test_admin">test_admin</a>')
306 306
307 307 response.mustcontain('gist-desc')
308 308
309 309 def test_show_without_hg(self, create_gist):
310 310 with mock.patch(
311 311 'rhodecode.lib.vcs.settings.ALIASES', ['git']):
312 312 gist = create_gist('gist-show-me-again')
313 313 self.app.get(
314 314 route_path('gist_show', gist_id=gist.gist_access_id), status=200)
315 315
316 316 def test_show_acl_private(self, create_gist):
317 317 gist = create_gist('gist-show-me-only-when-im-logged-in',
318 318 acl_level=Gist.ACL_LEVEL_PRIVATE)
319 319 self.app.get(
320 320 route_path('gist_show', gist_id=gist.gist_access_id), status=404)
321 321
322 322 # now we log-in we should see thi gist
323 323 self.log_user()
324 324 response = self.app.get(
325 325 route_path('gist_show', gist_id=gist.gist_access_id))
326 326 response.mustcontain('added file: gist-show-me-only-when-im-logged-in')
327 327
328 328 assert_response = response.assert_response()
329 329 assert_response.element_equals_to(
330 330 'div.rc-user span.user',
331 331 '<a href="/_profiles/test_admin">test_admin</a>')
332 332 response.mustcontain('gist-desc')
333 333
334 334 def test_show_as_raw(self, create_gist):
335 335 gist = create_gist('gist-show-me', content='GIST CONTENT')
336 336 response = self.app.get(
337 337 route_path('gist_show_formatted',
338 338 gist_id=gist.gist_access_id, revision='tip',
339 339 format='raw'))
340 340 assert response.body == 'GIST CONTENT'
341 341
342 342 def test_show_as_raw_individual_file(self, create_gist):
343 343 gist = create_gist('gist-show-me-raw', content='GIST BODY')
344 344 response = self.app.get(
345 345 route_path('gist_show_formatted_path',
346 346 gist_id=gist.gist_access_id, format='raw',
347 347 revision='tip', f_path='gist-show-me-raw'))
348 348 assert response.body == 'GIST BODY'
349 349
350 350 def test_edit_page(self, create_gist):
351 351 self.log_user()
352 352 gist = create_gist('gist-for-edit', content='GIST EDIT BODY')
353 353 response = self.app.get(route_path('gist_edit', gist_id=gist.gist_access_id))
354 354 response.mustcontain('GIST EDIT BODY')
355 355
356 356 def test_edit_page_non_logged_user(self, create_gist):
357 357 gist = create_gist('gist-for-edit', content='GIST EDIT BODY')
358 358 self.app.get(route_path('gist_edit', gist_id=gist.gist_access_id),
359 359 status=302)
360 360
361 361 def test_edit_normal_user_his_gist(self, create_gist):
362 362 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
363 363 gist = create_gist('gist-for-edit', owner=TEST_USER_REGULAR_LOGIN)
364 364 self.app.get(route_path('gist_edit', gist_id=gist.gist_access_id,
365 365 status=200))
366 366
367 367 def test_edit_normal_user_not_his_own_gist(self, create_gist):
368 368 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
369 369 gist = create_gist('delete-me')
370 370 self.app.get(route_path('gist_edit', gist_id=gist.gist_access_id),
371 371 status=404)
372 372
373 373 def test_user_first_name_is_escaped(self, user_util, create_gist):
374 374 xss_atack_string = '"><script>alert(\'First Name\')</script>'
375 375 xss_escaped_string = h.html_escape(h.escape(xss_atack_string))
376 376 password = 'test'
377 377 user = user_util.create_user(
378 378 firstname=xss_atack_string, password=password)
379 379 create_gist('gist', gist_type='public', owner=user.username)
380 380 response = self.app.get(route_path('gists_show'))
381 381 response.mustcontain(xss_escaped_string)
382 382
383 383 def test_user_last_name_is_escaped(self, user_util, create_gist):
384 384 xss_atack_string = '"><script>alert(\'Last Name\')</script>'
385 385 xss_escaped_string = h.html_escape(h.escape(xss_atack_string))
386 386 password = 'test'
387 387 user = user_util.create_user(
388 388 lastname=xss_atack_string, password=password)
389 389 create_gist('gist', gist_type='public', owner=user.username)
390 390 response = self.app.get(route_path('gists_show'))
391 391 response.mustcontain(xss_escaped_string)
@@ -1,419 +1,386 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2013-2020 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, HTTPBadRequest
29 from pyramid.view import view_config
30 29 from pyramid.renderers import render
31 30 from pyramid.response import Response
32 31
33 32 from rhodecode.apps._base import BaseAppView
34 33 from rhodecode.lib import helpers as h
35 34 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
36 35 from rhodecode.lib.utils2 import time_to_datetime
37 36 from rhodecode.lib.ext_json import json
38 37 from rhodecode.lib.vcs.exceptions import VCSError, NodeNotChangedError
39 38 from rhodecode.model.gist import GistModel
40 39 from rhodecode.model.meta import Session
41 40 from rhodecode.model.db import Gist, User, or_
42 41 from rhodecode.model import validation_schema
43 42 from rhodecode.model.validation_schema.schemas import gist_schema
44 43
45 44
46 45 log = logging.getLogger(__name__)
47 46
48 47
49 48 class GistView(BaseAppView):
50 49
51 50 def load_default_context(self):
52 51 _ = self.request.translate
53 52 c = self._get_local_tmpl_context()
54 53 c.user = c.auth_user.get_instance()
55 54
56 55 c.lifetime_values = [
57 56 (-1, _('forever')),
58 57 (5, _('5 minutes')),
59 58 (60, _('1 hour')),
60 59 (60 * 24, _('1 day')),
61 60 (60 * 24 * 30, _('1 month')),
62 61 ]
63 62
64 63 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
65 64 c.acl_options = [
66 65 (Gist.ACL_LEVEL_PRIVATE, _("Requires registered account")),
67 66 (Gist.ACL_LEVEL_PUBLIC, _("Can be accessed by anonymous users"))
68 67 ]
69 68
70 69 return c
71 70
72 71 @LoginRequired()
73 @view_config(
74 route_name='gists_show', request_method='GET',
75 renderer='rhodecode:templates/admin/gists/gist_index.mako')
76 72 def gist_show_all(self):
77 73 c = self.load_default_context()
78 74
79 75 not_default_user = self._rhodecode_user.username != User.DEFAULT_USER
80 76 c.show_private = self.request.GET.get('private') and not_default_user
81 77 c.show_public = self.request.GET.get('public') and not_default_user
82 78 c.show_all = self.request.GET.get('all') and self._rhodecode_user.admin
83 79
84 80 gists = _gists = Gist().query()\
85 81 .filter(or_(Gist.gist_expires == -1, Gist.gist_expires >= time.time()))\
86 82 .order_by(Gist.created_on.desc())
87 83
88 84 c.active = 'public'
89 85 # MY private
90 86 if c.show_private and not c.show_public:
91 87 gists = _gists.filter(Gist.gist_type == Gist.GIST_PRIVATE)\
92 88 .filter(Gist.gist_owner == self._rhodecode_user.user_id)
93 89 c.active = 'my_private'
94 90 # MY public
95 91 elif c.show_public and not c.show_private:
96 92 gists = _gists.filter(Gist.gist_type == Gist.GIST_PUBLIC)\
97 93 .filter(Gist.gist_owner == self._rhodecode_user.user_id)
98 94 c.active = 'my_public'
99 95 # MY public+private
100 96 elif c.show_private and c.show_public:
101 97 gists = _gists.filter(or_(Gist.gist_type == Gist.GIST_PUBLIC,
102 98 Gist.gist_type == Gist.GIST_PRIVATE))\
103 99 .filter(Gist.gist_owner == self._rhodecode_user.user_id)
104 100 c.active = 'my_all'
105 101 # Show all by super-admin
106 102 elif c.show_all:
107 103 c.active = 'all'
108 104 gists = _gists
109 105
110 106 # default show ALL public gists
111 107 if not c.show_public and not c.show_private and not c.show_all:
112 108 gists = _gists.filter(Gist.gist_type == Gist.GIST_PUBLIC)
113 109 c.active = 'public'
114 110
115 111 _render = self.request.get_partial_renderer(
116 112 'rhodecode:templates/data_table/_dt_elements.mako')
117 113
118 114 data = []
119 115
120 116 for gist in gists:
121 117 data.append({
122 118 'created_on': _render('gist_created', gist.created_on),
123 119 'created_on_raw': gist.created_on,
124 120 'type': _render('gist_type', gist.gist_type),
125 121 'access_id': _render('gist_access_id', gist.gist_access_id, gist.owner.full_contact),
126 122 'author': _render('gist_author', gist.owner.full_contact, gist.created_on, gist.gist_expires),
127 123 'author_raw': h.escape(gist.owner.full_contact),
128 124 'expires': _render('gist_expires', gist.gist_expires),
129 125 'description': _render('gist_description', gist.gist_description)
130 126 })
131 127 c.data = json.dumps(data)
132 128
133 129 return self._get_template_context(c)
134 130
135 131 @LoginRequired()
136 132 @NotAnonymous()
137 @view_config(
138 route_name='gists_new', request_method='GET',
139 renderer='rhodecode:templates/admin/gists/gist_new.mako')
140 133 def gist_new(self):
141 134 c = self.load_default_context()
142 135 return self._get_template_context(c)
143 136
144 137 @LoginRequired()
145 138 @NotAnonymous()
146 139 @CSRFRequired()
147 @view_config(
148 route_name='gists_create', request_method='POST',
149 renderer='rhodecode:templates/admin/gists/gist_new.mako')
150 140 def gist_create(self):
151 141 _ = self.request.translate
152 142 c = self.load_default_context()
153 143
154 144 data = dict(self.request.POST)
155 145 data['filename'] = data.get('filename') or Gist.DEFAULT_FILENAME
156 146
157 147 data['nodes'] = [{
158 148 'filename': data['filename'],
159 149 'content': data.get('content'),
160 150 'mimetype': data.get('mimetype') # None is autodetect
161 151 }]
162 152 gist_type = {
163 153 'public': Gist.GIST_PUBLIC,
164 154 'private': Gist.GIST_PRIVATE
165 155 }.get(data.get('gist_type')) or Gist.GIST_PRIVATE
166 156
167 157 data['gist_type'] = gist_type
168 158
169 159 data['gist_acl_level'] = (
170 160 data.get('gist_acl_level') or Gist.ACL_LEVEL_PRIVATE)
171 161
172 162 schema = gist_schema.GistSchema().bind(
173 163 lifetime_options=[x[0] for x in c.lifetime_values])
174 164
175 165 try:
176 166
177 167 schema_data = schema.deserialize(data)
178 168 # convert to safer format with just KEYs so we sure no duplicates
179 169 schema_data['nodes'] = gist_schema.sequence_to_nodes(
180 170 schema_data['nodes'])
181 171
182 172 gist = GistModel().create(
183 173 gist_id=schema_data['gistid'], # custom access id not real ID
184 174 description=schema_data['description'],
185 175 owner=self._rhodecode_user.user_id,
186 176 gist_mapping=schema_data['nodes'],
187 177 gist_type=schema_data['gist_type'],
188 178 lifetime=schema_data['lifetime'],
189 179 gist_acl_level=schema_data['gist_acl_level']
190 180 )
191 181 Session().commit()
192 182 new_gist_id = gist.gist_access_id
193 183 except validation_schema.Invalid as errors:
194 184 defaults = data
195 185 errors = errors.asdict()
196 186
197 187 if 'nodes.0.content' in errors:
198 188 errors['content'] = errors['nodes.0.content']
199 189 del errors['nodes.0.content']
200 190 if 'nodes.0.filename' in errors:
201 191 errors['filename'] = errors['nodes.0.filename']
202 192 del errors['nodes.0.filename']
203 193
204 194 data = render('rhodecode:templates/admin/gists/gist_new.mako',
205 195 self._get_template_context(c), self.request)
206 196 html = formencode.htmlfill.render(
207 197 data,
208 198 defaults=defaults,
209 199 errors=errors,
210 200 prefix_error=False,
211 201 encoding="UTF-8",
212 202 force_defaults=False
213 203 )
214 204 return Response(html)
215 205
216 206 except Exception:
217 207 log.exception("Exception while trying to create a gist")
218 208 h.flash(_('Error occurred during gist creation'), category='error')
219 209 raise HTTPFound(h.route_url('gists_new'))
220 210 raise HTTPFound(h.route_url('gist_show', gist_id=new_gist_id))
221 211
222 212 @LoginRequired()
223 213 @NotAnonymous()
224 214 @CSRFRequired()
225 @view_config(
226 route_name='gist_delete', request_method='POST')
227 215 def gist_delete(self):
228 216 _ = self.request.translate
229 217 gist_id = self.request.matchdict['gist_id']
230 218
231 219 c = self.load_default_context()
232 220 c.gist = Gist.get_or_404(gist_id)
233 221
234 222 owner = c.gist.gist_owner == self._rhodecode_user.user_id
235 223 if not (h.HasPermissionAny('hg.admin')() or owner):
236 224 log.warning('Deletion of Gist was forbidden '
237 225 'by unauthorized user: `%s`', self._rhodecode_user)
238 226 raise HTTPNotFound()
239 227
240 228 GistModel().delete(c.gist)
241 229 Session().commit()
242 230 h.flash(_('Deleted gist %s') % c.gist.gist_access_id, category='success')
243 231
244 232 raise HTTPFound(h.route_url('gists_show'))
245 233
246 234 def _get_gist(self, gist_id):
247 235
248 236 gist = Gist.get_or_404(gist_id)
249 237
250 238 # Check if this gist is expired
251 239 if gist.gist_expires != -1:
252 240 if time.time() > gist.gist_expires:
253 241 log.error(
254 242 'Gist expired at %s', time_to_datetime(gist.gist_expires))
255 243 raise HTTPNotFound()
256 244
257 245 # check if this gist requires a login
258 246 is_default_user = self._rhodecode_user.username == User.DEFAULT_USER
259 247 if gist.acl_level == Gist.ACL_LEVEL_PRIVATE and is_default_user:
260 248 log.error("Anonymous user %s tried to access protected gist `%s`",
261 249 self._rhodecode_user, gist_id)
262 250 raise HTTPNotFound()
263 251 return gist
264 252
265 253 @LoginRequired()
266 @view_config(
267 route_name='gist_show', request_method='GET',
268 renderer='rhodecode:templates/admin/gists/gist_show.mako')
269 @view_config(
270 route_name='gist_show_rev', request_method='GET',
271 renderer='rhodecode:templates/admin/gists/gist_show.mako')
272 @view_config(
273 route_name='gist_show_formatted', request_method='GET',
274 renderer=None)
275 @view_config(
276 route_name='gist_show_formatted_path', request_method='GET',
277 renderer=None)
278 254 def gist_show(self):
279 255 gist_id = self.request.matchdict['gist_id']
280 256
281 257 # TODO(marcink): expose those via matching dict
282 258 revision = self.request.matchdict.get('revision', 'tip')
283 259 f_path = self.request.matchdict.get('f_path', None)
284 260 return_format = self.request.matchdict.get('format')
285 261
286 262 c = self.load_default_context()
287 263 c.gist = self._get_gist(gist_id)
288 264 c.render = not self.request.GET.get('no-render', False)
289 265
290 266 try:
291 267 c.file_last_commit, c.files = GistModel().get_gist_files(
292 268 gist_id, revision=revision)
293 269 except VCSError:
294 270 log.exception("Exception in gist show")
295 271 raise HTTPNotFound()
296 272
297 273 if return_format == 'raw':
298 274 content = '\n\n'.join([f.content for f in c.files
299 275 if (f_path is None or f.path == f_path)])
300 276 response = Response(content)
301 277 response.content_type = 'text/plain'
302 278 return response
303 279 elif return_format:
304 280 raise HTTPBadRequest()
305 281
306 282 return self._get_template_context(c)
307 283
308 284 @LoginRequired()
309 285 @NotAnonymous()
310 @view_config(
311 route_name='gist_edit', request_method='GET',
312 renderer='rhodecode:templates/admin/gists/gist_edit.mako')
313 286 def gist_edit(self):
314 287 _ = self.request.translate
315 288 gist_id = self.request.matchdict['gist_id']
316 289 c = self.load_default_context()
317 290 c.gist = self._get_gist(gist_id)
318 291
319 292 owner = c.gist.gist_owner == self._rhodecode_user.user_id
320 293 if not (h.HasPermissionAny('hg.admin')() or owner):
321 294 raise HTTPNotFound()
322 295
323 296 try:
324 297 c.file_last_commit, c.files = GistModel().get_gist_files(gist_id)
325 298 except VCSError:
326 299 log.exception("Exception in gist edit")
327 300 raise HTTPNotFound()
328 301
329 302 if c.gist.gist_expires == -1:
330 303 expiry = _('never')
331 304 else:
332 305 # this cannot use timeago, since it's used in select2 as a value
333 306 expiry = h.age(h.time_to_datetime(c.gist.gist_expires))
334 307
335 308 c.lifetime_values.append(
336 309 (0, _('%(expiry)s - current value') % {'expiry': _(expiry)})
337 310 )
338 311
339 312 return self._get_template_context(c)
340 313
341 314 @LoginRequired()
342 315 @NotAnonymous()
343 316 @CSRFRequired()
344 @view_config(
345 route_name='gist_update', request_method='POST',
346 renderer='rhodecode:templates/admin/gists/gist_edit.mako')
347 317 def gist_update(self):
348 318 _ = self.request.translate
349 319 gist_id = self.request.matchdict['gist_id']
350 320 c = self.load_default_context()
351 321 c.gist = self._get_gist(gist_id)
352 322
353 323 owner = c.gist.gist_owner == self._rhodecode_user.user_id
354 324 if not (h.HasPermissionAny('hg.admin')() or owner):
355 325 raise HTTPNotFound()
356 326
357 327 data = peppercorn.parse(self.request.POST.items())
358 328
359 329 schema = gist_schema.GistSchema()
360 330 schema = schema.bind(
361 331 # '0' is special value to leave lifetime untouched
362 332 lifetime_options=[x[0] for x in c.lifetime_values] + [0],
363 333 )
364 334
365 335 try:
366 336 schema_data = schema.deserialize(data)
367 337 # convert to safer format with just KEYs so we sure no duplicates
368 338 schema_data['nodes'] = gist_schema.sequence_to_nodes(
369 339 schema_data['nodes'])
370 340
371 341 GistModel().update(
372 342 gist=c.gist,
373 343 description=schema_data['description'],
374 344 owner=c.gist.owner,
375 345 gist_mapping=schema_data['nodes'],
376 346 lifetime=schema_data['lifetime'],
377 347 gist_acl_level=schema_data['gist_acl_level']
378 348 )
379 349
380 350 Session().commit()
381 351 h.flash(_('Successfully updated gist content'), category='success')
382 352 except NodeNotChangedError:
383 353 # raised if nothing was changed in repo itself. We anyway then
384 354 # store only DB stuff for gist
385 355 Session().commit()
386 356 h.flash(_('Successfully updated gist data'), category='success')
387 357 except validation_schema.Invalid as errors:
388 358 errors = h.escape(errors.asdict())
389 359 h.flash(_('Error occurred during update of gist {}: {}').format(
390 360 gist_id, errors), category='error')
391 361 except Exception:
392 362 log.exception("Exception in gist edit")
393 363 h.flash(_('Error occurred during update of gist %s') % gist_id,
394 364 category='error')
395 365
396 366 raise HTTPFound(h.route_url('gist_show', gist_id=gist_id))
397 367
398 368 @LoginRequired()
399 369 @NotAnonymous()
400 @view_config(
401 route_name='gist_edit_check_revision', request_method='GET',
402 renderer='json_ext')
403 370 def gist_edit_check_revision(self):
404 371 _ = self.request.translate
405 372 gist_id = self.request.matchdict['gist_id']
406 373 c = self.load_default_context()
407 374 c.gist = self._get_gist(gist_id)
408 375
409 376 last_rev = c.gist.scm_instance().get_commit()
410 377 success = True
411 378 revision = self.request.GET.get('revision')
412 379
413 380 if revision != last_rev.raw_id:
414 381 log.error('Last revision %s is different then submitted %s',
415 382 revision, last_rev)
416 383 # our gist has newer version than we
417 384 success = False
418 385
419 386 return {'success': success}
@@ -1,93 +1,147 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2020 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 from rhodecode.config import routing_links
21 21
22 22
23 23 class VCSCallPredicate(object):
24 24 def __init__(self, val, config):
25 25 self.val = val
26 26
27 27 def text(self):
28 28 return 'vcs_call route = %s' % self.val
29 29
30 30 phash = text
31 31
32 32 def __call__(self, info, request):
33 33 if hasattr(request, 'vcs_call'):
34 34 # skip vcs calls
35 35 return False
36 36
37 37 return True
38 38
39 39
40 40 def includeme(config):
41 from rhodecode.apps.home.views import HomeView
42
43 config.add_route_predicate(
44 'skip_vcs_call', VCSCallPredicate)
41 45
42 46 config.add_route(
43 47 name='home',
44 48 pattern='/')
49 config.add_view(
50 HomeView,
51 attr='main_page',
52 route_name='home', request_method='GET',
53 renderer='rhodecode:templates/index.mako')
45 54
46 55 config.add_route(
47 56 name='main_page_repos_data',
48 57 pattern='/_home_repos')
58 config.add_view(
59 HomeView,
60 attr='main_page_repos_data',
61 route_name='main_page_repos_data',
62 request_method='GET', renderer='json_ext', xhr=True)
49 63
50 64 config.add_route(
51 65 name='main_page_repo_groups_data',
52 66 pattern='/_home_repo_groups')
67 config.add_view(
68 HomeView,
69 attr='main_page_repo_groups_data',
70 route_name='main_page_repo_groups_data',
71 request_method='GET', renderer='json_ext', xhr=True)
53 72
54 73 config.add_route(
55 74 name='user_autocomplete_data',
56 75 pattern='/_users')
76 config.add_view(
77 HomeView,
78 attr='user_autocomplete_data',
79 route_name='user_autocomplete_data', request_method='GET',
80 renderer='json_ext', xhr=True)
57 81
58 82 config.add_route(
59 83 name='user_group_autocomplete_data',
60 84 pattern='/_user_groups')
85 config.add_view(
86 HomeView,
87 attr='user_group_autocomplete_data',
88 route_name='user_group_autocomplete_data', request_method='GET',
89 renderer='json_ext', xhr=True)
61 90
62 91 config.add_route(
63 92 name='repo_list_data',
64 93 pattern='/_repos')
94 config.add_view(
95 HomeView,
96 attr='repo_list_data',
97 route_name='repo_list_data', request_method='GET',
98 renderer='json_ext', xhr=True)
65 99
66 100 config.add_route(
67 101 name='repo_group_list_data',
68 102 pattern='/_repo_groups')
103 config.add_view(
104 HomeView,
105 attr='repo_group_list_data',
106 route_name='repo_group_list_data', request_method='GET',
107 renderer='json_ext', xhr=True)
69 108
70 109 config.add_route(
71 110 name='goto_switcher_data',
72 111 pattern='/_goto_data')
112 config.add_view(
113 HomeView,
114 attr='goto_switcher_data',
115 route_name='goto_switcher_data', request_method='GET',
116 renderer='json_ext', xhr=True)
73 117
74 118 config.add_route(
75 119 name='markup_preview',
76 120 pattern='/_markup_preview')
121 config.add_view(
122 HomeView,
123 attr='markup_preview',
124 route_name='markup_preview', request_method='POST',
125 renderer='string', xhr=True)
77 126
78 127 config.add_route(
79 128 name='file_preview',
80 129 pattern='/_file_preview')
130 config.add_view(
131 HomeView,
132 attr='file_preview',
133 route_name='file_preview', request_method='POST',
134 renderer='string', xhr=True)
81 135
82 136 config.add_route(
83 137 name='store_user_session_value',
84 138 pattern='/_store_session_attr')
139 config.add_view(
140 HomeView,
141 attr='store_user_session_attr',
142 route_name='store_user_session_value', request_method='POST',
143 renderer='string', xhr=True)
85 144
86 145 # register our static links via redirection mechanism
87 146 routing_links.connect_redirection_links(config)
88 147
89 # Scan module for configuration decorators.
90 config.scan('.views', ignore='.tests')
91
92 config.add_route_predicate(
93 'skip_vcs_call', VCSCallPredicate)
@@ -1,897 +1,856 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2020 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 collections
24 24
25 25 from pyramid.httpexceptions import HTTPNotFound
26 from pyramid.view import view_config
27 26
28 27 from rhodecode.apps._base import BaseAppView, DataGridAppView
29 28 from rhodecode.lib import helpers as h
30 29 from rhodecode.lib.auth import (
31 30 LoginRequired, NotAnonymous, HasRepoGroupPermissionAnyDecorator, CSRFRequired,
32 31 HasRepoGroupPermissionAny, AuthUser)
33 32 from rhodecode.lib.codeblocks import filenode_as_lines_tokens
34 33 from rhodecode.lib.index import searcher_from_config
35 34 from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int, safe_str
36 35 from rhodecode.lib.vcs.nodes import FileNode
37 36 from rhodecode.model.db import (
38 37 func, true, or_, case, cast, in_filter_generator, String, Session,
39 38 Repository, RepoGroup, User, UserGroup, PullRequest)
40 39 from rhodecode.model.repo import RepoModel
41 40 from rhodecode.model.repo_group import RepoGroupModel
42 41 from rhodecode.model.user import UserModel
43 42 from rhodecode.model.user_group import UserGroupModel
44 43
45 44 log = logging.getLogger(__name__)
46 45
47 46
48 47 class HomeView(BaseAppView, DataGridAppView):
49 48
50 49 def load_default_context(self):
51 50 c = self._get_local_tmpl_context()
52 51 c.user = c.auth_user.get_instance()
53
54 52 return c
55 53
56 54 @LoginRequired()
57 @view_config(
58 route_name='user_autocomplete_data', request_method='GET',
59 renderer='json_ext', xhr=True)
60 55 def user_autocomplete_data(self):
61 56 self.load_default_context()
62 57 query = self.request.GET.get('query')
63 58 active = str2bool(self.request.GET.get('active') or True)
64 59 include_groups = str2bool(self.request.GET.get('user_groups'))
65 60 expand_groups = str2bool(self.request.GET.get('user_groups_expand'))
66 61 skip_default_user = str2bool(self.request.GET.get('skip_default_user'))
67 62
68 63 log.debug('generating user list, query:%s, active:%s, with_groups:%s',
69 64 query, active, include_groups)
70 65
71 66 _users = UserModel().get_users(
72 67 name_contains=query, only_active=active)
73 68
74 69 def maybe_skip_default_user(usr):
75 70 if skip_default_user and usr['username'] == UserModel.cls.DEFAULT_USER:
76 71 return False
77 72 return True
78 73 _users = filter(maybe_skip_default_user, _users)
79 74
80 75 if include_groups:
81 76 # extend with user groups
82 77 _user_groups = UserGroupModel().get_user_groups(
83 78 name_contains=query, only_active=active,
84 79 expand_groups=expand_groups)
85 80 _users = _users + _user_groups
86 81
87 82 return {'suggestions': _users}
88 83
89 84 @LoginRequired()
90 85 @NotAnonymous()
91 @view_config(
92 route_name='user_group_autocomplete_data', request_method='GET',
93 renderer='json_ext', xhr=True)
94 86 def user_group_autocomplete_data(self):
95 87 self.load_default_context()
96 88 query = self.request.GET.get('query')
97 89 active = str2bool(self.request.GET.get('active') or True)
98 90 expand_groups = str2bool(self.request.GET.get('user_groups_expand'))
99 91
100 92 log.debug('generating user group list, query:%s, active:%s',
101 93 query, active)
102 94
103 95 _user_groups = UserGroupModel().get_user_groups(
104 96 name_contains=query, only_active=active,
105 97 expand_groups=expand_groups)
106 98 _user_groups = _user_groups
107 99
108 100 return {'suggestions': _user_groups}
109 101
110 102 def _get_repo_list(self, name_contains=None, repo_type=None, repo_group_name='', limit=20):
111 103 org_query = name_contains
112 104 allowed_ids = self._rhodecode_user.repo_acl_ids(
113 105 ['repository.read', 'repository.write', 'repository.admin'],
114 106 cache=True, name_filter=name_contains) or [-1]
115 107
116 108 query = Session().query(
117 109 Repository.repo_name,
118 110 Repository.repo_id,
119 111 Repository.repo_type,
120 112 Repository.private,
121 113 )\
122 114 .filter(Repository.archived.isnot(true()))\
123 115 .filter(or_(
124 116 # generate multiple IN to fix limitation problems
125 117 *in_filter_generator(Repository.repo_id, allowed_ids)
126 118 ))
127 119
128 120 query = query.order_by(case(
129 121 [
130 122 (Repository.repo_name.startswith(repo_group_name), repo_group_name+'/'),
131 123 ],
132 124 ))
133 125 query = query.order_by(func.length(Repository.repo_name))
134 126 query = query.order_by(Repository.repo_name)
135 127
136 128 if repo_type:
137 129 query = query.filter(Repository.repo_type == repo_type)
138 130
139 131 if name_contains:
140 132 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
141 133 query = query.filter(
142 134 Repository.repo_name.ilike(ilike_expression))
143 135 query = query.limit(limit)
144 136
145 137 acl_iter = query
146 138
147 139 return [
148 140 {
149 141 'id': obj.repo_name,
150 142 'value': org_query,
151 143 'value_display': obj.repo_name,
152 144 'text': obj.repo_name,
153 145 'type': 'repo',
154 146 'repo_id': obj.repo_id,
155 147 'repo_type': obj.repo_type,
156 148 'private': obj.private,
157 149 'url': h.route_path('repo_summary', repo_name=obj.repo_name)
158 150 }
159 151 for obj in acl_iter]
160 152
161 153 def _get_repo_group_list(self, name_contains=None, repo_group_name='', limit=20):
162 154 org_query = name_contains
163 155 allowed_ids = self._rhodecode_user.repo_group_acl_ids(
164 156 ['group.read', 'group.write', 'group.admin'],
165 157 cache=True, name_filter=name_contains) or [-1]
166 158
167 159 query = Session().query(
168 160 RepoGroup.group_id,
169 161 RepoGroup.group_name,
170 162 )\
171 163 .filter(or_(
172 164 # generate multiple IN to fix limitation problems
173 165 *in_filter_generator(RepoGroup.group_id, allowed_ids)
174 166 ))
175 167
176 168 query = query.order_by(case(
177 169 [
178 170 (RepoGroup.group_name.startswith(repo_group_name), repo_group_name+'/'),
179 171 ],
180 172 ))
181 173 query = query.order_by(func.length(RepoGroup.group_name))
182 174 query = query.order_by(RepoGroup.group_name)
183 175
184 176 if name_contains:
185 177 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
186 178 query = query.filter(
187 179 RepoGroup.group_name.ilike(ilike_expression))
188 180 query = query.limit(limit)
189 181
190 182 acl_iter = query
191 183
192 184 return [
193 185 {
194 186 'id': obj.group_name,
195 187 'value': org_query,
196 188 'value_display': obj.group_name,
197 189 'text': obj.group_name,
198 190 'type': 'repo_group',
199 191 'repo_group_id': obj.group_id,
200 192 'url': h.route_path(
201 193 'repo_group_home', repo_group_name=obj.group_name)
202 194 }
203 195 for obj in acl_iter]
204 196
205 197 def _get_user_list(self, name_contains=None, limit=20):
206 198 org_query = name_contains
207 199 if not name_contains:
208 200 return [], False
209 201
210 202 # TODO(marcink): should all logged in users be allowed to search others?
211 203 allowed_user_search = self._rhodecode_user.username != User.DEFAULT_USER
212 204 if not allowed_user_search:
213 205 return [], False
214 206
215 207 name_contains = re.compile('(?:user:[ ]?)(.+)').findall(name_contains)
216 208 if len(name_contains) != 1:
217 209 return [], False
218 210
219 211 name_contains = name_contains[0]
220 212
221 213 query = User.query()\
222 214 .order_by(func.length(User.username))\
223 215 .order_by(User.username) \
224 216 .filter(User.username != User.DEFAULT_USER)
225 217
226 218 if name_contains:
227 219 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
228 220 query = query.filter(
229 221 User.username.ilike(ilike_expression))
230 222 query = query.limit(limit)
231 223
232 224 acl_iter = query
233 225
234 226 return [
235 227 {
236 228 'id': obj.user_id,
237 229 'value': org_query,
238 230 'value_display': 'user: `{}`'.format(obj.username),
239 231 'type': 'user',
240 232 'icon_link': h.gravatar_url(obj.email, 30),
241 233 'url': h.route_path(
242 234 'user_profile', username=obj.username)
243 235 }
244 236 for obj in acl_iter], True
245 237
246 238 def _get_user_groups_list(self, name_contains=None, limit=20):
247 239 org_query = name_contains
248 240 if not name_contains:
249 241 return [], False
250 242
251 243 # TODO(marcink): should all logged in users be allowed to search others?
252 244 allowed_user_search = self._rhodecode_user.username != User.DEFAULT_USER
253 245 if not allowed_user_search:
254 246 return [], False
255 247
256 248 name_contains = re.compile('(?:user_group:[ ]?)(.+)').findall(name_contains)
257 249 if len(name_contains) != 1:
258 250 return [], False
259 251
260 252 name_contains = name_contains[0]
261 253
262 254 query = UserGroup.query()\
263 255 .order_by(func.length(UserGroup.users_group_name))\
264 256 .order_by(UserGroup.users_group_name)
265 257
266 258 if name_contains:
267 259 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
268 260 query = query.filter(
269 261 UserGroup.users_group_name.ilike(ilike_expression))
270 262 query = query.limit(limit)
271 263
272 264 acl_iter = query
273 265
274 266 return [
275 267 {
276 268 'id': obj.users_group_id,
277 269 'value': org_query,
278 270 'value_display': 'user_group: `{}`'.format(obj.users_group_name),
279 271 'type': 'user_group',
280 272 'url': h.route_path(
281 273 'user_group_profile', user_group_name=obj.users_group_name)
282 274 }
283 275 for obj in acl_iter], True
284 276
285 277 def _get_pull_request_list(self, name_contains=None, limit=20):
286 278 org_query = name_contains
287 279 if not name_contains:
288 280 return [], False
289 281
290 282 # TODO(marcink): should all logged in users be allowed to search others?
291 283 allowed_user_search = self._rhodecode_user.username != User.DEFAULT_USER
292 284 if not allowed_user_search:
293 285 return [], False
294 286
295 287 name_contains = re.compile('(?:pr:[ ]?)(.+)').findall(name_contains)
296 288 if len(name_contains) != 1:
297 289 return [], False
298 290
299 291 name_contains = name_contains[0]
300 292
301 293 allowed_ids = self._rhodecode_user.repo_acl_ids(
302 294 ['repository.read', 'repository.write', 'repository.admin'],
303 295 cache=True) or [-1]
304 296
305 297 query = Session().query(
306 298 PullRequest.pull_request_id,
307 299 PullRequest.title,
308 300 )
309 301 query = query.join(Repository, Repository.repo_id == PullRequest.target_repo_id)
310 302
311 303 query = query.filter(or_(
312 304 # generate multiple IN to fix limitation problems
313 305 *in_filter_generator(Repository.repo_id, allowed_ids)
314 306 ))
315 307
316 308 query = query.order_by(PullRequest.pull_request_id)
317 309
318 310 if name_contains:
319 311 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
320 312 query = query.filter(or_(
321 313 cast(PullRequest.pull_request_id, String).ilike(ilike_expression),
322 314 PullRequest.title.ilike(ilike_expression),
323 315 PullRequest.description.ilike(ilike_expression),
324 316 ))
325 317
326 318 query = query.limit(limit)
327 319
328 320 acl_iter = query
329 321
330 322 return [
331 323 {
332 324 'id': obj.pull_request_id,
333 325 'value': org_query,
334 326 'value_display': 'pull request: `!{} - {}`'.format(
335 327 obj.pull_request_id, safe_str(obj.title[:50])),
336 328 'type': 'pull_request',
337 329 'url': h.route_path('pull_requests_global', pull_request_id=obj.pull_request_id)
338 330 }
339 331 for obj in acl_iter], True
340 332
341 333 def _get_hash_commit_list(self, auth_user, searcher, query, repo=None, repo_group=None):
342 334 repo_name = repo_group_name = None
343 335 if repo:
344 336 repo_name = repo.repo_name
345 337 if repo_group:
346 338 repo_group_name = repo_group.group_name
347 339
348 340 org_query = query
349 341 if not query or len(query) < 3 or not searcher:
350 342 return [], False
351 343
352 344 commit_hashes = re.compile('(?:commit:[ ]?)([0-9a-f]{2,40})').findall(query)
353 345
354 346 if len(commit_hashes) != 1:
355 347 return [], False
356 348
357 349 commit_hash = commit_hashes[0]
358 350
359 351 result = searcher.search(
360 352 'commit_id:{}*'.format(commit_hash), 'commit', auth_user,
361 353 repo_name, repo_group_name, raise_on_exc=False)
362 354
363 355 commits = []
364 356 for entry in result['results']:
365 357 repo_data = {
366 358 'repository_id': entry.get('repository_id'),
367 359 'repository_type': entry.get('repo_type'),
368 360 'repository_name': entry.get('repository'),
369 361 }
370 362
371 363 commit_entry = {
372 364 'id': entry['commit_id'],
373 365 'value': org_query,
374 366 'value_display': '`{}` commit: {}'.format(
375 367 entry['repository'], entry['commit_id']),
376 368 'type': 'commit',
377 369 'repo': entry['repository'],
378 370 'repo_data': repo_data,
379 371
380 372 'url': h.route_path(
381 373 'repo_commit',
382 374 repo_name=entry['repository'], commit_id=entry['commit_id'])
383 375 }
384 376
385 377 commits.append(commit_entry)
386 378 return commits, True
387 379
388 380 def _get_path_list(self, auth_user, searcher, query, repo=None, repo_group=None):
389 381 repo_name = repo_group_name = None
390 382 if repo:
391 383 repo_name = repo.repo_name
392 384 if repo_group:
393 385 repo_group_name = repo_group.group_name
394 386
395 387 org_query = query
396 388 if not query or len(query) < 3 or not searcher:
397 389 return [], False
398 390
399 391 paths_re = re.compile('(?:file:[ ]?)(.+)').findall(query)
400 392 if len(paths_re) != 1:
401 393 return [], False
402 394
403 395 file_path = paths_re[0]
404 396
405 397 search_path = searcher.escape_specials(file_path)
406 398 result = searcher.search(
407 399 'file.raw:*{}*'.format(search_path), 'path', auth_user,
408 400 repo_name, repo_group_name, raise_on_exc=False)
409 401
410 402 files = []
411 403 for entry in result['results']:
412 404 repo_data = {
413 405 'repository_id': entry.get('repository_id'),
414 406 'repository_type': entry.get('repo_type'),
415 407 'repository_name': entry.get('repository'),
416 408 }
417 409
418 410 file_entry = {
419 411 'id': entry['commit_id'],
420 412 'value': org_query,
421 413 'value_display': '`{}` file: {}'.format(
422 414 entry['repository'], entry['file']),
423 415 'type': 'file',
424 416 'repo': entry['repository'],
425 417 'repo_data': repo_data,
426 418
427 419 'url': h.route_path(
428 420 'repo_files',
429 421 repo_name=entry['repository'], commit_id=entry['commit_id'],
430 422 f_path=entry['file'])
431 423 }
432 424
433 425 files.append(file_entry)
434 426 return files, True
435 427
436 428 @LoginRequired()
437 @view_config(
438 route_name='repo_list_data', request_method='GET',
439 renderer='json_ext', xhr=True)
440 429 def repo_list_data(self):
441 430 _ = self.request.translate
442 431 self.load_default_context()
443 432
444 433 query = self.request.GET.get('query')
445 434 repo_type = self.request.GET.get('repo_type')
446 435 log.debug('generating repo list, query:%s, repo_type:%s',
447 436 query, repo_type)
448 437
449 438 res = []
450 439 repos = self._get_repo_list(query, repo_type=repo_type)
451 440 if repos:
452 441 res.append({
453 442 'text': _('Repositories'),
454 443 'children': repos
455 444 })
456 445
457 446 data = {
458 447 'more': False,
459 448 'results': res
460 449 }
461 450 return data
462 451
463 452 @LoginRequired()
464 @view_config(
465 route_name='repo_group_list_data', request_method='GET',
466 renderer='json_ext', xhr=True)
467 453 def repo_group_list_data(self):
468 454 _ = self.request.translate
469 455 self.load_default_context()
470 456
471 457 query = self.request.GET.get('query')
472 458
473 459 log.debug('generating repo group list, query:%s',
474 460 query)
475 461
476 462 res = []
477 463 repo_groups = self._get_repo_group_list(query)
478 464 if repo_groups:
479 465 res.append({
480 466 'text': _('Repository Groups'),
481 467 'children': repo_groups
482 468 })
483 469
484 470 data = {
485 471 'more': False,
486 472 'results': res
487 473 }
488 474 return data
489 475
490 476 def _get_default_search_queries(self, search_context, searcher, query):
491 477 if not searcher:
492 478 return []
493 479
494 480 is_es_6 = searcher.is_es_6
495 481
496 482 queries = []
497 483 repo_group_name, repo_name, repo_context = None, None, None
498 484
499 485 # repo group context
500 486 if search_context.get('search_context[repo_group_name]'):
501 487 repo_group_name = search_context.get('search_context[repo_group_name]')
502 488 if search_context.get('search_context[repo_name]'):
503 489 repo_name = search_context.get('search_context[repo_name]')
504 490 repo_context = search_context.get('search_context[repo_view_type]')
505 491
506 492 if is_es_6 and repo_name:
507 493 # files
508 494 def query_modifier():
509 495 qry = query
510 496 return {'q': qry, 'type': 'content'}
511 497
512 498 label = u'File content search for `{}`'.format(h.escape(query))
513 499 file_qry = {
514 500 'id': -10,
515 501 'value': query,
516 502 'value_display': label,
517 503 'value_icon': '<i class="icon-code"></i>',
518 504 'type': 'search',
519 505 'subtype': 'repo',
520 506 'url': h.route_path('search_repo',
521 507 repo_name=repo_name,
522 508 _query=query_modifier())
523 509 }
524 510
525 511 # commits
526 512 def query_modifier():
527 513 qry = query
528 514 return {'q': qry, 'type': 'commit'}
529 515
530 516 label = u'Commit search for `{}`'.format(h.escape(query))
531 517 commit_qry = {
532 518 'id': -20,
533 519 'value': query,
534 520 'value_display': label,
535 521 'value_icon': '<i class="icon-history"></i>',
536 522 'type': 'search',
537 523 'subtype': 'repo',
538 524 'url': h.route_path('search_repo',
539 525 repo_name=repo_name,
540 526 _query=query_modifier())
541 527 }
542 528
543 529 if repo_context in ['commit', 'commits']:
544 530 queries.extend([commit_qry, file_qry])
545 531 elif repo_context in ['files', 'summary']:
546 532 queries.extend([file_qry, commit_qry])
547 533 else:
548 534 queries.extend([commit_qry, file_qry])
549 535
550 536 elif is_es_6 and repo_group_name:
551 537 # files
552 538 def query_modifier():
553 539 qry = query
554 540 return {'q': qry, 'type': 'content'}
555 541
556 542 label = u'File content search for `{}`'.format(query)
557 543 file_qry = {
558 544 'id': -30,
559 545 'value': query,
560 546 'value_display': label,
561 547 'value_icon': '<i class="icon-code"></i>',
562 548 'type': 'search',
563 549 'subtype': 'repo_group',
564 550 'url': h.route_path('search_repo_group',
565 551 repo_group_name=repo_group_name,
566 552 _query=query_modifier())
567 553 }
568 554
569 555 # commits
570 556 def query_modifier():
571 557 qry = query
572 558 return {'q': qry, 'type': 'commit'}
573 559
574 560 label = u'Commit search for `{}`'.format(query)
575 561 commit_qry = {
576 562 'id': -40,
577 563 'value': query,
578 564 'value_display': label,
579 565 'value_icon': '<i class="icon-history"></i>',
580 566 'type': 'search',
581 567 'subtype': 'repo_group',
582 568 'url': h.route_path('search_repo_group',
583 569 repo_group_name=repo_group_name,
584 570 _query=query_modifier())
585 571 }
586 572
587 573 if repo_context in ['commit', 'commits']:
588 574 queries.extend([commit_qry, file_qry])
589 575 elif repo_context in ['files', 'summary']:
590 576 queries.extend([file_qry, commit_qry])
591 577 else:
592 578 queries.extend([commit_qry, file_qry])
593 579
594 580 # Global, not scoped
595 581 if not queries:
596 582 queries.append(
597 583 {
598 584 'id': -1,
599 585 'value': query,
600 586 'value_display': u'File content search for: `{}`'.format(query),
601 587 'value_icon': '<i class="icon-code"></i>',
602 588 'type': 'search',
603 589 'subtype': 'global',
604 590 'url': h.route_path('search',
605 591 _query={'q': query, 'type': 'content'})
606 592 })
607 593 queries.append(
608 594 {
609 595 'id': -2,
610 596 'value': query,
611 597 'value_display': u'Commit search for: `{}`'.format(query),
612 598 'value_icon': '<i class="icon-history"></i>',
613 599 'type': 'search',
614 600 'subtype': 'global',
615 601 'url': h.route_path('search',
616 602 _query={'q': query, 'type': 'commit'})
617 603 })
618 604
619 605 return queries
620 606
621 607 @LoginRequired()
622 @view_config(
623 route_name='goto_switcher_data', request_method='GET',
624 renderer='json_ext', xhr=True)
625 608 def goto_switcher_data(self):
626 609 c = self.load_default_context()
627 610
628 611 _ = self.request.translate
629 612
630 613 query = self.request.GET.get('query')
631 614 log.debug('generating main filter data, query %s', query)
632 615
633 616 res = []
634 617 if not query:
635 618 return {'suggestions': res}
636 619
637 620 def no_match(name):
638 621 return {
639 622 'id': -1,
640 623 'value': "",
641 624 'value_display': name,
642 625 'type': 'text',
643 626 'url': ""
644 627 }
645 628 searcher = searcher_from_config(self.request.registry.settings)
646 629 has_specialized_search = False
647 630
648 631 # set repo context
649 632 repo = None
650 633 repo_id = safe_int(self.request.GET.get('search_context[repo_id]'))
651 634 if repo_id:
652 635 repo = Repository.get(repo_id)
653 636
654 637 # set group context
655 638 repo_group = None
656 639 repo_group_id = safe_int(self.request.GET.get('search_context[repo_group_id]'))
657 640 if repo_group_id:
658 641 repo_group = RepoGroup.get(repo_group_id)
659 642 prefix_match = False
660 643
661 644 # user: type search
662 645 if not prefix_match:
663 646 users, prefix_match = self._get_user_list(query)
664 647 if users:
665 648 has_specialized_search = True
666 649 for serialized_user in users:
667 650 res.append(serialized_user)
668 651 elif prefix_match:
669 652 has_specialized_search = True
670 653 res.append(no_match('No matching users found'))
671 654
672 655 # user_group: type search
673 656 if not prefix_match:
674 657 user_groups, prefix_match = self._get_user_groups_list(query)
675 658 if user_groups:
676 659 has_specialized_search = True
677 660 for serialized_user_group in user_groups:
678 661 res.append(serialized_user_group)
679 662 elif prefix_match:
680 663 has_specialized_search = True
681 664 res.append(no_match('No matching user groups found'))
682 665
683 666 # pr: type search
684 667 if not prefix_match:
685 668 pull_requests, prefix_match = self._get_pull_request_list(query)
686 669 if pull_requests:
687 670 has_specialized_search = True
688 671 for serialized_pull_request in pull_requests:
689 672 res.append(serialized_pull_request)
690 673 elif prefix_match:
691 674 has_specialized_search = True
692 675 res.append(no_match('No matching pull requests found'))
693 676
694 677 # FTS commit: type search
695 678 if not prefix_match:
696 679 commits, prefix_match = self._get_hash_commit_list(
697 680 c.auth_user, searcher, query, repo, repo_group)
698 681 if commits:
699 682 has_specialized_search = True
700 683 unique_repos = collections.OrderedDict()
701 684 for commit in commits:
702 685 repo_name = commit['repo']
703 686 unique_repos.setdefault(repo_name, []).append(commit)
704 687
705 688 for _repo, commits in unique_repos.items():
706 689 for commit in commits:
707 690 res.append(commit)
708 691 elif prefix_match:
709 692 has_specialized_search = True
710 693 res.append(no_match('No matching commits found'))
711 694
712 695 # FTS file: type search
713 696 if not prefix_match:
714 697 paths, prefix_match = self._get_path_list(
715 698 c.auth_user, searcher, query, repo, repo_group)
716 699 if paths:
717 700 has_specialized_search = True
718 701 unique_repos = collections.OrderedDict()
719 702 for path in paths:
720 703 repo_name = path['repo']
721 704 unique_repos.setdefault(repo_name, []).append(path)
722 705
723 706 for repo, paths in unique_repos.items():
724 707 for path in paths:
725 708 res.append(path)
726 709 elif prefix_match:
727 710 has_specialized_search = True
728 711 res.append(no_match('No matching files found'))
729 712
730 713 # main suggestions
731 714 if not has_specialized_search:
732 715 repo_group_name = ''
733 716 if repo_group:
734 717 repo_group_name = repo_group.group_name
735 718
736 719 for _q in self._get_default_search_queries(self.request.GET, searcher, query):
737 720 res.append(_q)
738 721
739 722 repo_groups = self._get_repo_group_list(query, repo_group_name=repo_group_name)
740 723 for serialized_repo_group in repo_groups:
741 724 res.append(serialized_repo_group)
742 725
743 726 repos = self._get_repo_list(query, repo_group_name=repo_group_name)
744 727 for serialized_repo in repos:
745 728 res.append(serialized_repo)
746 729
747 730 if not repos and not repo_groups:
748 731 res.append(no_match('No matches found'))
749 732
750 733 return {'suggestions': res}
751 734
752 735 @LoginRequired()
753 @view_config(
754 route_name='home', request_method='GET',
755 renderer='rhodecode:templates/index.mako')
756 736 def main_page(self):
757 737 c = self.load_default_context()
758 738 c.repo_group = None
759 739 return self._get_template_context(c)
760 740
761 741 def _main_page_repo_groups_data(self, repo_group_id):
762 742 column_map = {
763 743 'name': 'group_name_hash',
764 744 'desc': 'group_description',
765 745 'last_change': 'updated_on',
766 746 'owner': 'user_username',
767 747 }
768 748 draw, start, limit = self._extract_chunk(self.request)
769 749 search_q, order_by, order_dir = self._extract_ordering(
770 750 self.request, column_map=column_map)
771 751 return RepoGroupModel().get_repo_groups_data_table(
772 752 draw, start, limit,
773 753 search_q, order_by, order_dir,
774 754 self._rhodecode_user, repo_group_id)
775 755
776 756 def _main_page_repos_data(self, repo_group_id):
777 757 column_map = {
778 758 'name': 'repo_name',
779 759 'desc': 'description',
780 760 'last_change': 'updated_on',
781 761 'owner': 'user_username',
782 762 }
783 763 draw, start, limit = self._extract_chunk(self.request)
784 764 search_q, order_by, order_dir = self._extract_ordering(
785 765 self.request, column_map=column_map)
786 766 return RepoModel().get_repos_data_table(
787 767 draw, start, limit,
788 768 search_q, order_by, order_dir,
789 769 self._rhodecode_user, repo_group_id)
790 770
791 771 @LoginRequired()
792 @view_config(
793 route_name='main_page_repo_groups_data',
794 request_method='GET', renderer='json_ext', xhr=True)
795 772 def main_page_repo_groups_data(self):
796 773 self.load_default_context()
797 774 repo_group_id = safe_int(self.request.GET.get('repo_group_id'))
798 775
799 776 if repo_group_id:
800 777 group = RepoGroup.get_or_404(repo_group_id)
801 778 _perms = AuthUser.repo_group_read_perms
802 779 if not HasRepoGroupPermissionAny(*_perms)(
803 780 group.group_name, 'user is allowed to list repo group children'):
804 781 raise HTTPNotFound()
805 782
806 783 return self._main_page_repo_groups_data(repo_group_id)
807 784
808 785 @LoginRequired()
809 @view_config(
810 route_name='main_page_repos_data',
811 request_method='GET', renderer='json_ext', xhr=True)
812 786 def main_page_repos_data(self):
813 787 self.load_default_context()
814 788 repo_group_id = safe_int(self.request.GET.get('repo_group_id'))
815 789
816 790 if repo_group_id:
817 791 group = RepoGroup.get_or_404(repo_group_id)
818 792 _perms = AuthUser.repo_group_read_perms
819 793 if not HasRepoGroupPermissionAny(*_perms)(
820 794 group.group_name, 'user is allowed to list repo group children'):
821 795 raise HTTPNotFound()
822 796
823 797 return self._main_page_repos_data(repo_group_id)
824 798
825 799 @LoginRequired()
826 800 @HasRepoGroupPermissionAnyDecorator(*AuthUser.repo_group_read_perms)
827 @view_config(
828 route_name='repo_group_home', request_method='GET',
829 renderer='rhodecode:templates/index_repo_group.mako')
830 @view_config(
831 route_name='repo_group_home_slash', request_method='GET',
832 renderer='rhodecode:templates/index_repo_group.mako')
833 801 def repo_group_main_page(self):
834 802 c = self.load_default_context()
835 803 c.repo_group = self.request.db_repo_group
836 804 return self._get_template_context(c)
837 805
838 806 @LoginRequired()
839 807 @CSRFRequired()
840 @view_config(
841 route_name='markup_preview', request_method='POST',
842 renderer='string', xhr=True)
843 808 def markup_preview(self):
844 809 # Technically a CSRF token is not needed as no state changes with this
845 810 # call. However, as this is a POST is better to have it, so automated
846 811 # tools don't flag it as potential CSRF.
847 812 # Post is required because the payload could be bigger than the maximum
848 813 # allowed by GET.
849 814
850 815 text = self.request.POST.get('text')
851 816 renderer = self.request.POST.get('renderer') or 'rst'
852 817 if text:
853 818 return h.render(text, renderer=renderer, mentions=True)
854 819 return ''
855 820
856 821 @LoginRequired()
857 822 @CSRFRequired()
858 @view_config(
859 route_name='file_preview', request_method='POST',
860 renderer='string', xhr=True)
861 823 def file_preview(self):
862 824 # Technically a CSRF token is not needed as no state changes with this
863 825 # call. However, as this is a POST is better to have it, so automated
864 826 # tools don't flag it as potential CSRF.
865 827 # Post is required because the payload could be bigger than the maximum
866 828 # allowed by GET.
867 829
868 830 text = self.request.POST.get('text')
869 831 file_path = self.request.POST.get('file_path')
870 832
871 833 renderer = h.renderer_from_filename(file_path)
872 834
873 835 if renderer:
874 836 return h.render(text, renderer=renderer, mentions=True)
875 837 else:
876 838 self.load_default_context()
877 839 _render = self.request.get_partial_renderer(
878 840 'rhodecode:templates/files/file_content.mako')
879 841
880 842 lines = filenode_as_lines_tokens(FileNode(file_path, text))
881 843
882 844 return _render('render_lines', lines)
883 845
884 846 @LoginRequired()
885 847 @CSRFRequired()
886 @view_config(
887 route_name='store_user_session_value', request_method='POST',
888 renderer='string', xhr=True)
889 848 def store_user_session_attr(self):
890 849 key = self.request.POST.get('key')
891 850 val = self.request.POST.get('val')
892 851
893 852 existing_value = self.request.session.get(key)
894 853 if existing_value != val:
895 854 self.request.session[key] = val
896 855
897 856 return 'stored:{}:{}'.format(key, val)
@@ -1,45 +1,67 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2018-2020 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 def includeme(config):
23
23 from rhodecode.apps.hovercards.views import HoverCardsView, HoverCardsRepoView
24 24 config.add_route(
25 25 name='hovercard_user',
26 26 pattern='/_hovercard/user/{user_id}')
27 config.add_view(
28 HoverCardsView,
29 attr='hovercard_user',
30 route_name='hovercard_user', request_method='GET', xhr=True,
31 renderer='rhodecode:templates/hovercards/hovercard_user.mako')
27 32
28 33 config.add_route(
29 34 name='hovercard_username',
30 35 pattern='/_hovercard/username/{username}')
36 config.add_view(
37 HoverCardsView,
38 attr='hovercard_username',
39 route_name='hovercard_username', request_method='GET', xhr=True,
40 renderer='rhodecode:templates/hovercards/hovercard_user.mako')
31 41
32 42 config.add_route(
33 43 name='hovercard_user_group',
34 44 pattern='/_hovercard/user_group/{user_group_id}')
45 config.add_view(
46 HoverCardsView,
47 attr='hovercard_user_group',
48 route_name='hovercard_user_group', request_method='GET', xhr=True,
49 renderer='rhodecode:templates/hovercards/hovercard_user_group.mako')
35 50
36 51 config.add_route(
37 52 name='hovercard_pull_request',
38 53 pattern='/_hovercard/pull_request/{pull_request_id}')
54 config.add_view(
55 HoverCardsView,
56 attr='hovercard_pull_request',
57 route_name='hovercard_pull_request', request_method='GET', xhr=True,
58 renderer='rhodecode:templates/hovercards/hovercard_pull_request.mako')
39 59
40 60 config.add_route(
41 61 name='hovercard_repo_commit',
42 62 pattern='/_hovercard/commit/{repo_name:.*?[^/]}/{commit_id}', repo_route=True)
43
44 # Scan module for configuration decorators.
45 config.scan('.views', ignore='.tests')
63 config.add_view(
64 HoverCardsRepoView,
65 attr='hovercard_repo_commit',
66 route_name='hovercard_repo_commit', request_method='GET', xhr=True,
67 renderer='rhodecode:templates/hovercards/hovercard_repo_commit.mako')
@@ -1,123 +1,108 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2020 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 collections
24 24
25 25 from pyramid.httpexceptions import HTTPNotFound
26 from pyramid.view import view_config
26
27 27
28 28 from rhodecode.apps._base import BaseAppView, RepoAppView
29 29 from rhodecode.lib import helpers as h
30 30 from rhodecode.lib.auth import (
31 31 LoginRequired, NotAnonymous, HasRepoGroupPermissionAnyDecorator, CSRFRequired,
32 32 HasRepoPermissionAnyDecorator)
33 33 from rhodecode.lib.codeblocks import filenode_as_lines_tokens
34 34 from rhodecode.lib.index import searcher_from_config
35 35 from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int
36 36 from rhodecode.lib.ext_json import json
37 37 from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError, EmptyRepositoryError
38 38 from rhodecode.lib.vcs.nodes import FileNode
39 39 from rhodecode.model.db import (
40 40 func, true, or_, case, in_filter_generator, Repository, RepoGroup, User, UserGroup, PullRequest)
41 41 from rhodecode.model.repo import RepoModel
42 42 from rhodecode.model.repo_group import RepoGroupModel
43 43 from rhodecode.model.scm import RepoGroupList, RepoList
44 44 from rhodecode.model.user import UserModel
45 45 from rhodecode.model.user_group import UserGroupModel
46 46
47 47 log = logging.getLogger(__name__)
48 48
49 49
50 50 class HoverCardsView(BaseAppView):
51 51
52 52 def load_default_context(self):
53 53 c = self._get_local_tmpl_context()
54 54 return c
55 55
56 56 @LoginRequired()
57 @view_config(
58 route_name='hovercard_user', request_method='GET', xhr=True,
59 renderer='rhodecode:templates/hovercards/hovercard_user.mako')
60 57 def hovercard_user(self):
61 58 c = self.load_default_context()
62 59 user_id = self.request.matchdict['user_id']
63 60 c.user = User.get_or_404(user_id)
64 61 return self._get_template_context(c)
65 62
66 63 @LoginRequired()
67 @view_config(
68 route_name='hovercard_username', request_method='GET', xhr=True,
69 renderer='rhodecode:templates/hovercards/hovercard_user.mako')
70 64 def hovercard_username(self):
71 65 c = self.load_default_context()
72 66 username = self.request.matchdict['username']
73 67 c.user = User.get_by_username(username)
74 68 if not c.user:
75 69 raise HTTPNotFound()
76 70
77 71 return self._get_template_context(c)
78 72
79 73 @LoginRequired()
80 @view_config(
81 route_name='hovercard_user_group', request_method='GET', xhr=True,
82 renderer='rhodecode:templates/hovercards/hovercard_user_group.mako')
83 74 def hovercard_user_group(self):
84 75 c = self.load_default_context()
85 76 user_group_id = self.request.matchdict['user_group_id']
86 77 c.user_group = UserGroup.get_or_404(user_group_id)
87 78 return self._get_template_context(c)
88 79
89 80 @LoginRequired()
90 @view_config(
91 route_name='hovercard_pull_request', request_method='GET', xhr=True,
92 renderer='rhodecode:templates/hovercards/hovercard_pull_request.mako')
93 81 def hovercard_pull_request(self):
94 82 c = self.load_default_context()
95 83 c.pull_request = PullRequest.get_or_404(
96 84 self.request.matchdict['pull_request_id'])
97 85 perms = ['repository.read', 'repository.write', 'repository.admin']
98 86 c.can_view_pr = h.HasRepoPermissionAny(*perms)(
99 87 c.pull_request.target_repo.repo_name)
100 88 return self._get_template_context(c)
101 89
102 90
103 91 class HoverCardsRepoView(RepoAppView):
104 92 def load_default_context(self):
105 93 c = self._get_local_tmpl_context()
106 94 return c
107 95
108 96 @LoginRequired()
109 97 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', 'repository.admin')
110 @view_config(
111 route_name='hovercard_repo_commit', request_method='GET', xhr=True,
112 renderer='rhodecode:templates/hovercards/hovercard_repo_commit.mako')
113 98 def hovercard_repo_commit(self):
114 99 c = self.load_default_context()
115 100 commit_id = self.request.matchdict['commit_id']
116 101 pre_load = ['author', 'branch', 'date', 'message']
117 102 try:
118 103 c.commit = self.rhodecode_vcs_repo.get_commit(
119 104 commit_id=commit_id, pre_load=pre_load)
120 105 except (CommitDoesNotExistError, EmptyRepositoryError):
121 106 raise HTTPNotFound()
122 107
123 108 return self._get_template_context(c)
@@ -1,53 +1,102 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2020 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 from rhodecode.apps._base import ADMIN_PREFIX
23 23
24 24
25 25 def admin_routes(config):
26 from rhodecode.apps.journal.views import JournalView
26 27
27 28 config.add_route(
28 29 name='journal', pattern='/journal')
30 config.add_view(
31 JournalView,
32 attr='journal',
33 route_name='journal', request_method='GET',
34 renderer=None)
35
29 36 config.add_route(
30 37 name='journal_rss', pattern='/journal/rss')
38 config.add_view(
39 JournalView,
40 attr='journal_rss',
41 route_name='journal_rss', request_method='GET',
42 renderer=None)
43
31 44 config.add_route(
32 45 name='journal_atom', pattern='/journal/atom')
46 config.add_view(
47 JournalView,
48 attr='journal_atom',
49 route_name='journal_atom', request_method='GET',
50 renderer=None)
33 51
34 52 config.add_route(
35 53 name='journal_public', pattern='/public_journal')
54 config.add_view(
55 JournalView,
56 attr='journal_public',
57 route_name='journal_public', request_method='GET',
58 renderer=None)
59
36 60 config.add_route(
37 61 name='journal_public_atom', pattern='/public_journal/atom')
62 config.add_view(
63 JournalView,
64 attr='journal_public_atom',
65 route_name='journal_public_atom', request_method='GET',
66 renderer=None)
67
38 68 config.add_route(
39 69 name='journal_public_atom_old', pattern='/public_journal_atom')
70 config.add_view(
71 JournalView,
72 attr='journal_public_atom',
73 route_name='journal_public_atom_old', request_method='GET',
74 renderer=None)
40 75
41 76 config.add_route(
42 77 name='journal_public_rss', pattern='/public_journal/rss')
78 config.add_view(
79 JournalView,
80 attr='journal_public_rss',
81 route_name='journal_public_rss', request_method='GET',
82 renderer=None)
83
43 84 config.add_route(
44 85 name='journal_public_rss_old', pattern='/public_journal_rss')
86 config.add_view(
87 JournalView,
88 attr='journal_public_rss',
89 route_name='journal_public_rss_old', request_method='GET',
90 renderer=None)
45 91
46 92 config.add_route(
47 93 name='toggle_following', pattern='/toggle_following')
94 config.add_view(
95 JournalView,
96 attr='toggle_following',
97 route_name='toggle_following', request_method='POST',
98 renderer='json_ext')
48 99
49 100
50 101 def includeme(config):
51 102 config.include(admin_routes, route_prefix=ADMIN_PREFIX)
52 # Scan module for configuration decorators.
53 config.scan('.views', ignore='.tests')
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
General Comments 0
You need to be logged in to leave comments. Login now