Compare commits

..

246 Commits
5.2.0 ... 5.4.0

Author SHA1 Message Date
Rafa de la Torre
70dab149ba Update release date in NEWS.md 2018-03-15 12:11:03 +01:00
Rafa de la Torre
ed4b44a78a Merge pull request #905 from CartoDB/mapnik-3.6.2-carto.4
Upgrade @carto/mapnik to 3.6.2-carto.4
2018-03-15 12:08:32 +01:00
Rafa de la Torre
de1aaf3808 Update image because of new mapnik cache 2018-03-15 09:55:07 +01:00
Rafa de la Torre
769aee1107 Do not use the @github: notation in yarn.lock
The little advantage is that from a clean dir a `yarn install` won't
modify the `yarn.lock` file.
2018-03-15 09:36:35 +01:00
Rafa de la Torre
3663e6d12a More accurate msg in NEWS.md 2018-03-15 09:33:23 +01:00
Rafa de la Torre
a6d9984453 Upgrade @carto/mapnik to 3.6.2-carto.4 2018-03-15 09:29:08 +01:00
Daniel
f49d7478d7 Merge pull request #899 from CartoDB/refactor-named-maps-admin
Named Maps Admin: Extract middlewares form controller's context
2018-03-14 17:55:03 +01:00
Daniel
b4a1c9d648 Merge pull request #898 from CartoDB/refactor-analysis-controller
Refactor analysis controller
2018-03-14 17:50:15 +01:00
Daniel
692246ec44 Merge pull request #901 from CartoDB/refactor-named-maps-controller
Refactor named maps controller
2018-03-14 17:50:06 +01:00
Daniel García Aubert
04146f897d Use template strings 2018-03-14 17:33:54 +01:00
Daniel García Aubert
a34658c97f Use objects instead of param list 2018-03-14 17:31:37 +01:00
Daniel García Aubert
8d37e00869 Use objects instead of parameter list 2018-03-14 17:25:58 +01:00
Raúl Marín
584d6ae9cf Merge pull request #902 from CartoDB/camshaft0614
Update camshaft to 0.61.4
2018-03-14 17:25:25 +01:00
Daniel García Aubert
421e611356 Use objects instead of a list of parameters 2018-03-14 17:22:47 +01:00
Daniel García Aubert
f078713d28 typo 2018-03-14 17:15:50 +01:00
Daniel García Aubert
a8d31d52cf Pass only required params 2018-03-14 17:08:04 +01:00
Raul Marin
d9213b2fe2 Update camshaft to 0.61.4 2018-03-14 15:56:35 +01:00
Daniel García Aubert
091efe52fc Extract sendResponse middleware from context 2018-03-14 13:32:43 +01:00
Daniel García Aubert
a5c508733a Extract setContentTypeHeader middleware from controller's context 2018-03-14 13:31:39 +01:00
Daniel García Aubert
ce944d9a7d Extract setCacheControlHeader from controller's context 2018-03-14 13:30:27 +01:00
Daniel García Aubert
8321b5adba Extract setLastModifiedHeader from controller's context 2018-03-14 13:27:56 +01:00
Daniel García Aubert
667c972308 Extract setCacheChannelHeader and setSurrogateKeyHeader from controller's context 2018-03-14 13:25:42 +01:00
Daniel García Aubert
3dbe05be3a Extarct setCacheChannelHeader middleware from controller's context 2018-03-14 13:19:56 +01:00
Daniel García Aubert
2d4ce19250 Extract incrementMapViews middleware from controllers middleware 2018-03-14 13:18:37 +01:00
Daniel García Aubert
3b3e0c0acd Extract getImage middleware from controller's context 2018-03-14 13:15:38 +01:00
Daniel García Aubert
c3ddb933bb Extract getStaticImageOptions middleware from controller's context 2018-03-14 13:11:17 +01:00
Daniel García Aubert
6aae60ece7 Extract getTile middleware from controller's context 2018-03-14 13:07:40 +01:00
Daniel García Aubert
6b3dc8ece0 Extract prepareLayerFilterFromPreviewLayers middleware from controller's context 2018-03-14 13:05:05 +01:00
Daniel García Aubert
7dd231a8c9 Extract getTemplate middleware form controller's context 2018-03-14 13:02:49 +01:00
Daniel García Aubert
35a3219012 Extract getAffectedTables middleware from controller's context 2018-03-14 13:01:07 +01:00
Daniel García Aubert
7598e6ab4b Extract getNamedMapProvider middleware from controller's context 2018-03-14 12:58:56 +01:00
Raúl Marín
75870dc6c1 Merge pull request #900 from Algunenano/windshaft457
Update Windshaft to 4.5.7
2018-03-14 10:39:05 +01:00
Raul Marin
42900b5d0e Update Windshaft to 4.5.7 2018-03-14 10:28:34 +01:00
Daniel García Aubert
2cc4161239 Missing semicolon 2018-03-13 15:02:44 +01:00
Daniel García Aubert
fc8f3fdf27 Create send response middleware 2018-03-13 13:31:49 +01:00
Daniel García Aubert
24b76208ac Improve naming 2018-03-13 13:21:40 +01:00
Daniel García Aubert
0de272b195 Extract middlewares form controller's context 2018-03-13 13:12:18 +01:00
Daniel
7faf40004c Merge pull request #891 from CartoDB/refactor-map-controler
Refactor map controler
2018-03-13 12:44:53 +01:00
Daniel García Aubert
88ae2d473a Typo 2018-03-13 11:43:08 +01:00
Daniel García Aubert
337b47685c Remove controler context from middlewares 2018-03-13 11:42:25 +01:00
Raúl Marín
ed3f9be655 Merge pull request #897 from Algunenano/windshaft456
Update Windshaft to 4.5.6
2018-03-12 19:21:09 +01:00
Raul Marin
248c6d5f22 Update Windshaft to 4.5.6 2018-03-12 19:13:56 +01:00
Raúl Marín
dd4aa09d21 Merge pull request #896 from Algunenano/master_camshaft0613
Update camshaft to 0.61.3
2018-03-12 17:02:25 +01:00
Raul Marin
132f2226ca Update camshaft to 0.61.3 2018-03-12 16:52:43 +01:00
Daniel
2eb6e95fed Merge pull request #895 from CartoDB/fix-performance-regression
Fix performance regression
2018-03-12 15:48:20 +01:00
Daniel García Aubert
db8130be4f Honor jshint 2018-03-12 14:18:07 +01:00
Daniel García Aubert
379b649e95 Don't get mapconfig prematurely 2018-03-12 13:29:40 +01:00
Raúl Marín
e5619492ef Merge pull request #894 from Algunenano/master_windshaft455
Update Windshaft to 4.5.5
2018-03-12 13:23:15 +01:00
Daniel García Aubert
cc76ccc626 Fix undeclared variable 2018-03-12 13:10:20 +01:00
Raul Marin
3d6512dd11 Update Windshaft to 4.5.5 2018-03-12 13:01:17 +01:00
Eneko Lakasta
b19d97e01f Merge pull request #886 from CartoDB/vary-header
Vary header
2018-03-12 12:46:42 +01:00
Daniel García Aubert
25931a618b Do not calculate affected tables when there are no affacted tables 2018-03-12 12:33:29 +01:00
Eneko Lakasta
ffab576399 Merge branch 'master' into vary-header 2018-03-12 12:31:23 +01:00
Raúl Marín
e7067ab9cf Merge pull request #893 from Algunenano/master_request_update
Update request to 2.85.0
2018-03-12 12:25:19 +01:00
Eneko Lakasta
7cfcf6d579 merge master 2018-03-12 12:16:55 +01:00
Raul Marin
a4b586055a Update request to 2.85.0 2018-03-12 12:16:29 +01:00
Eneko Lakasta
5ad1e1b645 merge master 2018-03-12 11:52:38 +01:00
Daniel García Aubert
01ed513a79 Use 'const' 2018-03-09 17:02:13 +01:00
Daniel García Aubert
504f68b8aa Missing semicolon 2018-03-09 16:18:33 +01:00
Daniel García Aubert
cbb08f5642 Extract function 2018-03-09 15:49:03 +01:00
Raúl Marín
a4b5d681ce Merge pull request #890 from Algunenano/windshaft_454
Update Windshaft to 4.5.4
2018-03-09 13:12:37 +01:00
Daniel García Aubert
02f93f3a14 Extract layergroup-metadata class 2018-03-09 12:58:05 +01:00
Raul Marin
ad2f4573f8 Update Windshaft to 4.5.4 2018-03-09 11:44:42 +01:00
Raúl Marín
06604cd738 Merge pull request #888 from Algunenano/support_1368
Aggregation count: Do not return null categories
2018-03-09 11:06:40 +01:00
Raul Marin
089be35b5d Aggregation count: Do not return null categories 2018-03-08 18:13:20 +01:00
Daniel García Aubert
bbcb335d60 Merge branch 'master' into refactor-map-controler 2018-03-08 13:18:43 +01:00
Daniel
6ef2e0bb5f Merge pull request #887 from CartoDB/middlewarify-layergroup-controller
Middlewarify layergroup controller
2018-03-08 13:04:39 +01:00
Daniel García Aubert
d8202d881d Remove legacy test 2018-03-08 12:46:04 +01:00
Daniel García Aubert
aae814a156 Use template strings 2018-03-08 12:35:54 +01:00
Daniel García Aubert
49bcc5368d Use base number as radix to pare intergers 2018-03-08 12:30:27 +01:00
Daniel García Aubert
555e04f9e7 Use ternary operator 2018-03-08 12:27:49 +01:00
Daniel García Aubert
3f6f2e4e23 Use template string 2018-03-08 12:23:43 +01:00
Daniel García Aubert
abffc4b067 Uppercase for actual constants 2018-03-08 12:23:00 +01:00
Daniel García Aubert
363cb0b679 Extract middlewares from map-controller class 2018-03-08 12:16:24 +01:00
Daniel García Aubert
d26910ba9c Extract checkJsonContentType middleware from MapController class 2018-03-07 19:11:03 +01:00
Daniel García Aubert
74b2f305ea Extract initProfiler middleware from map-controller 2018-03-07 19:09:52 +01:00
Daniel García Aubert
6c2f893651 Rename map-store-map-config-provider middleware 2018-03-07 18:53:20 +01:00
Daniel García Aubert
faaf121eb6 Rename center and bbox middlewares 2018-03-07 18:51:43 +01:00
Daniel García Aubert
83ab65163d Rename attributes middleware 2018-03-07 18:43:35 +01:00
Daniel García Aubert
9dcd5ff332 Impreve naming 2018-03-07 15:56:16 +01:00
Daniel García Aubert
c6635f63c1 Unify layer and tile middlewares 2018-03-07 15:39:59 +01:00
Daniel García Aubert
56213219e4 Rename middleware 2018-03-07 15:25:30 +01:00
Daniel García Aubert
7c2dc20dbe Merge branch 'master' into middlewarify-layergroup-controller 2018-03-07 15:24:34 +01:00
Daniel García Aubert
c8e8317ea4 Do not attach middleware to LayergroupController classs 2018-03-07 15:20:47 +01:00
Daniel
8509796743 Merge pull request #882 from CartoDB/middleware-refactor
Middleware refactor
2018-03-07 15:19:04 +01:00
Daniel García Aubert
90aaed0f2c Typo 2018-03-07 15:05:36 +01:00
Daniel García Aubert
48be15b742 Use const in favour of var 2018-03-07 15:01:04 +01:00
Daniel García Aubert
a95b3f2f99 Fix comment 2018-03-07 14:54:09 +01:00
Daniel García Aubert
b2cc7ab84f Move functions to improve readablity 2018-03-07 14:53:13 +01:00
Daniel García Aubert
eb3414f07f Follow middleware pattern 2018-03-07 14:48:21 +01:00
Daniel García Aubert
292dad130d Move middlewares to the right place 2018-03-07 14:42:21 +01:00
Daniel García Aubert
ec41cddb19 Do not pass the whole res.locals to backends 2018-03-07 12:52:44 +01:00
Daniel García Aubert
5871f8290d Use default param values 2018-03-07 12:46:18 +01:00
Daniel García Aubert
33089be2cd Do not attach header middlewares to node status endpoint 2018-03-07 12:30:59 +01:00
Daniel García Aubert
d351c8d14c Define var as const 2018-03-07 12:09:41 +01:00
Daniel García Aubert
82446e5ffa Use template string to define routes 2018-03-07 12:05:53 +01:00
Daniel García Aubert
b786164e8a Middlewarify metrics increment whether success or error 2018-03-07 11:56:57 +01:00
Eneko Lakasta
f9cbb3aac8 use assert.equal instead of assert.ok 2018-03-07 10:51:17 +01:00
Daniel García Aubert
a66c19c6c7 Do not bind context when unneeded 2018-03-06 20:05:55 +01:00
Daniel García Aubert
94d1667d70 Refactor affected tables 2018-03-06 20:01:43 +01:00
Daniel García Aubert
3399db1cff Add comment 2018-03-06 18:58:09 +01:00
Daniel García Aubert
874ea99d19 Remove step 2018-03-06 18:43:23 +01:00
Daniel García Aubert
7022fb87b4 Extract header, affected-tables and response middlewares 2018-03-06 18:28:40 +01:00
Daniel García Aubert
7c1e2a6af0 Avoid nested steps 2018-03-06 17:08:39 +01:00
Daniel García Aubert
2f011c3266 Remove nested steps 2018-03-06 17:01:51 +01:00
Daniel García Aubert
4762aa0897 Remove step from sendResponse function 2018-03-06 16:55:27 +01:00
Daniel García Aubert
f30f83331f Extract tile error middleware 2018-03-06 16:44:37 +01:00
Daniel García Aubert
3695e1e3e5 Place function closer to where is called 2018-03-06 16:21:46 +01:00
Daniel García Aubert
585b5929aa Middlewarify tile and layer endpoints 2018-03-06 16:19:53 +01:00
Eneko Lakasta
0185cdf785 please jshint 2018-03-06 15:34:29 +01:00
Eneko Lakasta
8d22ca66ba fix tests 2018-03-06 15:26:35 +01:00
Eneko Lakasta
b0eacb2a79 add vary header to honor authorization header when caching 2018-03-06 12:46:38 +01:00
Daniel García Aubert
9b40370794 Now that mapConfigProvider is linked to 'res.locals' do not pass the whole 'res.locals' to map-config-provider to avoid converting circular structure to JSON 2018-03-06 12:44:17 +01:00
Daniel García Aubert
95f3d58383 Make jshint happy 2018-03-05 19:33:46 +01:00
Daniel García Aubert
0f0cde1093 Middlewarify static-api (bbox/center) endpoints 2018-03-05 19:26:26 +01:00
Daniel García Aubert
ca56df5cfe Middlewarify attributes endpoint 2018-03-05 18:28:52 +01:00
Daniel García Aubert
d8a4209768 Middlewarify analysis-node-status endpoint 2018-03-05 18:13:19 +01:00
Daniel García Aubert
40712a2e62 Middlewarify search dataview endpoint 2018-03-05 18:05:42 +01:00
Daniel García Aubert
acb9ce33b1 Pass dataview-backend as middleware option 2018-03-05 18:04:50 +01:00
Daniel García Aubert
5e43a7145a Middlewarify dataview endpoint 2018-03-05 17:44:04 +01:00
Daniel
39bd6694f2 Merge pull request #883 from CartoDB/fix-named-map-format
Do not force format for default named tiles
2018-03-05 11:29:43 +01:00
Daniel García Aubert
5de8c4f9c3 Make explicit that forceFormat is optional 2018-03-05 11:19:17 +01:00
Daniel García Aubert
f03d98cd0d Going green: fix test 2018-03-02 18:37:13 +01:00
Daniel García Aubert
6331bebb30 Going green: be able to request defaul named tiles in vector format 2018-03-02 18:25:23 +01:00
Daniel García Aubert
fdd4c4aaa0 Going red: get default named map vector tile 2018-03-02 18:22:53 +01:00
Daniel García Aubert
8656fcd8d1 Use 'const' 2018-03-02 14:04:29 +01:00
Daniel García Aubert
f2f6b9d49c ES6 goodies 2018-03-02 13:29:30 +01:00
Daniel García Aubert
82f1e6753b Remove unreachable code 2018-03-02 13:14:02 +01:00
Daniel García Aubert
7ed717607a Missing space before paramenter list 2018-03-02 13:08:57 +01:00
Daniel García Aubert
0ec9491d21 Fix test: Add stub for profiling 2018-03-02 11:16:46 +01:00
Daniel García Aubert
416970c819 Remove empty line 2018-03-01 19:10:35 +01:00
Daniel García Aubert
ccc28f3617 Add profiler step to lzma 2018-03-01 19:09:11 +01:00
Daniel García Aubert
5bac36b30f Remove bad profiler usage 2018-03-01 18:53:05 +01:00
Daniel García Aubert
ef3ffddec7 Cosmetic changes 2018-03-01 18:49:44 +01:00
Daniel García Aubert
e6ba467d98 ES6 goodies 2018-03-01 18:47:07 +01:00
Daniel García Aubert
314508bcd8 Middleware naming convention 2018-03-01 18:46:04 +01:00
Daniel García Aubert
da18506e41 Follow middleware factory pattern 2018-03-01 18:45:04 +01:00
Daniel García Aubert
5eaee0b71e Follow middleware naming convention 2018-03-01 18:12:07 +01:00
Daniel García Aubert
bd93e7dc7e Follow middleware pattern 2018-03-01 18:09:49 +01:00
Daniel García Aubert
2c762813ba Follow middleware pattern, return a function as the actual middleware 2018-03-01 15:52:48 +01:00
Daniel García Aubert
3caa1d9c4a ES6 cosmetics 2018-03-01 15:42:46 +01:00
Daniel García Aubert
b0c924ca03 Follow middleware pattern, should return a function as the actual middleware 2018-03-01 15:42:03 +01:00
Daniel García Aubert
f6f59023b4 Ungroup middlewares 2018-02-28 19:46:46 +01:00
Daniel García Aubert
9dc4e7c955 Use the right step name for profiling 2018-02-28 19:29:53 +01:00
Daniel García Aubert
faa44e54ae Cosmetic changes 2018-02-28 19:29:10 +01:00
Daniel García Aubert
bfb743b851 Improve profiling steps 2018-02-28 19:27:49 +01:00
Daniel García Aubert
dad2e92dd3 Follow middleware naming convention 2018-02-28 19:26:47 +01:00
Daniel García Aubert
59c312ea40 Require modules at the beginning of module 2018-02-28 19:25:50 +01:00
Daniel García Aubert
48c5a458f3 Remove bad use of profiling step 2018-02-28 19:22:22 +01:00
Daniel García Aubert
c0830862c8 Follow middleware naming convention 2018-02-28 19:21:44 +01:00
Daniel García Aubert
42deb7abbe Rename middleware 2018-02-28 19:20:51 +01:00
Daniel García Aubert
62deda6470 Improve naming 2018-02-28 19:13:49 +01:00
Daniel García Aubert
3e0981978a Update NEWS 2018-02-28 17:31:03 +01:00
Eneko Lakasta
35e5170907 Merge pull request #877 from CartoDB/project-auth-api
Auth API
2018-02-28 17:20:21 +01:00
Eneko Lakasta
8eba5dcc01 update cartodb-redis to 0.16.0 2018-02-28 17:07:31 +01:00
Daniel García Aubert
5c2248d419 Merge branch 'master' into project-auth-api 2018-02-28 14:43:12 +01:00
Daniel García Aubert
102b11b1b5 Follow middleware naming convention 2018-02-28 13:10:46 +01:00
Eneko Lakasta
26df09b13f require debug at the top of file 2018-02-28 11:42:44 +01:00
Eneko Lakasta
8867cdbc02 use anonymous function instead of arrow function in middleware export to don't bind this 2018-02-26 15:57:42 +01:00
Eneko Lakasta
521b441da5 default apikey is returned by metadata module if no apikey found, remove this code because is never going to be run 2018-02-20 12:53:33 +01:00
Eneko Lakasta
59ca00b33b move apikey credentials getter to middleware file 2018-02-20 12:31:36 +01:00
Daniel García Aubert
7c7d606aa7 Remove trailing spaces 2018-02-19 19:05:13 +01:00
Daniel
46587e3cf1 Merge pull request #874 from CartoDB/project-auth-api-fallback
add fallback for using metadata fallback
2018-02-19 18:55:24 +01:00
Daniel García Aubert
603ef4044c Reduce cyclomatic complexity 2018-02-19 18:48:02 +01:00
Daniel García Aubert
2e3abfb2cd Catch "name not found" errors from metadata backend and set http code status 404 2018-02-19 18:28:58 +01:00
Daniel García Aubert
47ccb7ded8 Point to main development branch of cartodb-redis 2018-02-19 17:57:46 +01:00
Eneko Lakasta
7e14247ea9 remove cause of unreachable code/dead code. Not necessary because carto redis assures at least the default api key 2018-02-19 17:06:59 +01:00
Eneko Lakasta
5823859b2a update package.json to use the same branch form carto redis 2018-02-16 12:00:05 +01:00
Eneko Lakasta
7b21bd26d0 Merge branch 'project-auth-api' into project-auth-api-fallback 2018-02-16 11:29:36 +01:00
Eneko Lakasta
4ac224688c in fallback mode, use default api key if api key token doesnt exist 2018-02-16 11:20:04 +01:00
Eneko Lakasta
e84d88b7a3 remove test 2018-02-15 17:53:15 +01:00
Eneko Lakasta
cda2616a8a get and check api key credentials from api key: username and token 2018-02-15 17:49:47 +01:00
Eneko Lakasta
11aa4d12bd add tests for getting api key token from requests 2018-02-15 15:27:41 +01:00
Eneko Lakasta
18dbeea003 get apikey token from request in named maps admin middleware 2018-02-15 15:20:52 +01:00
Eneko Lakasta
3e916c6054 check if req.query exist before getting req.query.api_key/map_key 2018-02-15 15:20:05 +01:00
Eneko Lakasta
fc420c2c0f use for compatibility res.locals.api_key instead of res.locals.apikeyToken 2018-02-15 15:19:09 +01:00
Eneko Lakasta
140441b777 fix test 2018-02-15 12:53:01 +01:00
Eneko Lakasta
5db0e9c8d8 add middleware for apikeyToken 2018-02-15 12:50:42 +01:00
Eneko Lakasta
963737d3fb create lib for getting api key token from request 2018-02-15 11:36:42 +01:00
Eneko Lakasta
890f0d1ef6 add fallback for using metadata fallback 2018-02-14 17:31:05 +01:00
Raul Marin
6b0ab45e63 Update Windshaft to 4.5.3 2018-02-13 12:09:59 +01:00
Rafa de la Torre
0b475ab5e2 Stub next version 2018-02-13 09:07:48 +01:00
Rafa de la Torre
97972ac73f Update NEWS.md with v5.3.1 2018-02-13 09:05:06 +01:00
Rafa de la Torre
c3b38b2f60 Merge pull request #865 from CartoDB/perf-boost-agg-dataview
Improve the speed of the aggregation dataview
2018-02-13 09:04:00 +01:00
Rafa de la Torre
b4e06ec1ac Update NEWS.md and stub version 2018-02-12 19:35:34 +01:00
Rafa de la Torre
d0a8bd428f Merge remote-tracking branch 'origin/master' into perf-boost-agg-dataview 2018-02-12 19:33:35 +01:00
Rafa de la Torre
251fe96509 Cosmetic fix, as suggested in PR 2018-02-12 19:24:53 +01:00
Eneko Lakasta
32986e3ebd Merge branch 'master' into project-auth-api 2018-02-12 12:25:46 +01:00
Eneko Lakasta
f106f27df4 Merge branch 'master' into project-auth-api 2018-02-12 12:21:09 +01:00
Raul Marin
3539b658fb Update Windshaft to 4.5.2 2018-02-12 12:19:36 +01:00
Javier Goizueta
abf33a1c68 Release 5.3.0 2018-02-12 12:03:02 +01:00
Javier Goizueta
fc6790ea1e Merge pull request #866 from CartoDB/update-camshaft-to-0.61.2
Update camshaft to 0.61.2
2018-02-12 11:56:31 +01:00
Raul Marin
ac6f0e1c67 Update Windshaft to 4.5.1 2018-02-12 11:35:46 +01:00
Eneko Lakasta
041cd40ec2 please jshint 2018-02-09 19:26:52 +01:00
Eneko Lakasta
8721f56269 add auth test for getting tiles 2018-02-09 19:19:18 +01:00
Eneko Lakasta
1d3045c799 add tests should create/fail creating named maps and regular api key 2018-02-09 12:33:33 +01:00
Daniel García Aubert
52a1ed869c Update to development version of cartodb-redis 2018-02-08 19:28:43 +01:00
Eneko Lakasta
04f60baec5 Set the master role inheritance from regular roles as TBA 2018-02-08 19:01:58 +01:00
Eneko Lakasta
a8de436424 add test should create a layergroup with a buffer analysis using a regular apikey token AND grant privileges to master and regular roles in bootstraping sql 2018-02-08 18:54:14 +01:00
Javier Goizueta
ee7917676b Update camshaft to 0.61.2
This is to fixe a bug in the line-sequential analyses (length was incorrect)
2018-02-08 17:00:13 +01:00
Eneko Lakasta
c7780e9f42 add tests should create a layergroup with a source analysis 2018-02-08 15:20:43 +01:00
Eneko Lakasta
3edd7b8b01 add test should create a layergroup with default apikey token 2018-02-08 14:50:17 +01:00
Eneko Lakasta
455202cd1a organize prepare db api keys 2018-02-08 14:49:42 +01:00
Eneko Lakasta
8bdb82c7be add test should fail creating a layergroup with default apikey token 2018-02-08 14:48:00 +01:00
Eneko Lakasta
fa503ee66a fix test typo 2018-02-08 14:43:12 +01:00
Eneko Lakasta
e1a2ee2381 control API access grants 2018-02-08 13:07:25 +01:00
Eneko Lakasta
b82d26527a remove comment 2018-02-08 12:35:44 +01:00
Eneko Lakasta
1c50dd6b48 add first tests for auth 2018-02-08 12:34:24 +01:00
Eneko Lakasta
b0e9df1400 add pgConnection.getDatabaseParams 2018-02-08 12:04:03 +01:00
Eneko Lakasta
6ebf51ce45 let select apikey type in setDBAuth: regular, default, master 2018-02-08 11:29:17 +01:00
Eneko Lakasta
d9a34f3384 add cartodb250user api keys to redis 2018-02-08 11:13:21 +01:00
Eneko Lakasta
8136a1e136 fix test 2018-02-07 19:12:26 +01:00
Eneko Lakasta
41f3606572 return unauthorized error when api key not found 2018-02-07 19:12:14 +01:00
Eneko Lakasta
ea0542dcb1 remove use of step 2018-02-07 18:48:59 +01:00
Eneko Lakasta
a4dbc1bac2 remove step and check existance of proper api key 2018-02-07 18:20:56 +01:00
Rafa de la Torre
065f56e161 Improve the speed of the aggregation dataview
Improve the performance of the aggregation dataview.

Instead of using a CTE (WITH) for filtered_source, which is only used in
one place to calculate ranks, inject it as a subquery.

This way the planner has a chance to ignore uneeded columns as well as
to parallelize the exectution of the window function (WindowAgg in the
query plan).

That is the part that takes most of the time of the query.

The improvement is about 20-40% in speed on PG10 with 4 cores.
2018-02-07 18:10:13 +01:00
Eneko Lakasta
6b5d6648de fix unit test 2018-02-07 17:14:46 +01:00
Eneko Lakasta
95538707c9 add parameter asMaster to setDBAuth 2018-02-07 17:14:13 +01:00
Eneko Lakasta
4c76a921b1 use res.locals instead of req.params 2018-02-07 16:02:13 +01:00
Eneko Lakasta
85c1c987af refactor setDBConn to not use step 2018-02-07 15:49:32 +01:00
Eneko Lakasta
bde86323fd use master api key in setDBAuth 2018-02-07 15:36:24 +01:00
Eneko Lakasta
880e3f388d remove use of _.extend calls 2018-02-07 12:46:10 +01:00
Eneko Lakasta
c1535b1a12 refactor setDBAuth to not use step 2018-02-07 12:40:36 +01:00
Eneko Lakasta
232ff1ba33 add apikeys keys to be removed after each test 2018-02-07 11:59:00 +01:00
Eneko Lakasta
1b63dcd4e5 add api keys to prepare db for testing 2018-02-07 11:10:50 +01:00
Raúl Marín
b32a0a6547 Merge pull request #864 from Algunenano/master_metrics
Add metrics option to Mapnik renderer (off by default)
2018-02-06 16:48:22 +01:00
Raul Marin
d634be0c30 Update NEWs and Windshaft to 4.5.0 2018-02-06 16:10:44 +01:00
Raul Marin
f9fe3ace37 Add mapnik metrics option 2018-02-06 16:07:22 +01:00
Simon Martín
6cd8131888 updating NEWS 2018-02-06 11:30:34 +01:00
Simon Martín
0ea76f7d15 Merge pull request #849 from CartoDB/redis4
Redis4 support
2018-02-06 11:24:02 +01:00
Simon Martín
51e5b5c255 upgrading cartodb-redis, redis-mpool and windshaft versions 2018-02-05 18:09:14 +01:00
Simon Martín
cedcc094e6 Merge branch 'master' into redis4 2018-02-05 12:42:59 +01:00
Javier Goizueta
bbe8d4e820 Stub next version 2018-02-01 16:39:43 +01:00
Javier Goizueta
5aa98c4ab2 Release 5.2.1 2018-02-01 16:37:05 +01:00
Javier Goizueta
d6a9103779 Merge pull request #861 from CartoDB/860-aggregation-min-res
Limit the minimum size of the the aggregation grid
2018-02-01 16:32:35 +01:00
Javier Goizueta
2e7784ddf2 Add comment to clafify aggregation resolution limit 2018-02-01 10:26:52 +01:00
Raul Marin
086be461b2 Stub next version 2018-02-01 09:33:18 +01:00
Javier Goizueta
80604b739a Add test for aggregation with attributes
This reveals #860
2018-01-31 18:56:24 +01:00
Javier Goizueta
d88fbbaa87 Use camelCase 2018-01-31 18:55:28 +01:00
Javier Goizueta
7db0744f67 Simplify expression 2018-01-31 17:54:40 +01:00
Javier Goizueta
d1fcd797a3 Limit the minimum size of the the aggregation grid
Fixes #860
2018-01-31 17:46:13 +01:00
Simon Martín
2c703e5c16 updating node-redis-mpool dev version 2018-01-19 14:40:27 +01:00
Simon Martín
e68ba95fed adding cartodb-redis with redis4 support 2018-01-18 15:16:40 +01:00
Simon Martín
83d00a8aca Merge branch 'master' into redis4 2018-01-17 10:29:32 +01:00
Simon Martín
479e8970a1 Merge branch 'master' into redis4 2018-01-16 15:10:08 +01:00
Simon Martín
cf5e797f90 supporting redis4 2018-01-16 11:59:23 +01:00
51 changed files with 2771 additions and 1339 deletions

36
NEWS.md
View File

@@ -1,5 +1,41 @@
# Changelog
## 5.4.0
Released 2018-03-15
- Upgrades Windshaft to 4.5.7 ([Mapnik top metrics](https://github.com/CartoDB/Windshaft/pull/597), [AttributesBackend allows multiple features if all the attributes are the same](https://github.com/CartoDB/Windshaft/pull/602))
- Implemented middleware to authorize users via new Api Key system
- Keep the old authorization system as fallback
- Aggregation widget: Remove NULL categories in 'count' aggregations too
- Update request to 2.85.0
- Update camshaft to 0.61.4 (Fixes for AOI and Merge analyses)
- Update windshaft to 4.6.0, which in turn updates @carto/mapnik to 3.6.2-carto.4 and related dependencies. It brings in a cache for rasterized symbols. See https://github.com/CartoDB/node-mapnik/blob/v3.6.2-carto/CHANGELOG.carto.md#362-carto4
- PostGIS: Variables in postgis SQL queries must now additionally be wrapped in `!` (refs [#29](https://github.com/CartoDB/mapnik/issues/29), [mapnik/#3618](https://github.com/mapnik/mapnik/pull/3618)):
```sql
-- Before
SELECT ... WHERE trait = @variable
-- Now
SELECT ... WHERE trait = !@variable!
```
## 5.3.1
Released 2018-02-13
- Improve the speed of the aggregation dataview #865
## 5.3.0
Released 2018-02-12
- Upgrades redis-mpool to 0.5.0
- Upgrades windshaft to 4.5.2
- Upgrades cartodb-redis to 0.15.0
- Adds metrics option to the Mapnik renderer
- Upgrades camshadft to 0.61.2
## 5.2.1
Released 2018-02-01
Bug Fixes:
- Allow use of aggregation with attributes #861
## 5.2.0
Released 2018-02-01

View File

@@ -205,11 +205,12 @@ var config = {
// much closer to the original geometry
removeRepeatedPoints: false // this requires postgis >=2.2
},
// If enabled Mapnik will reuse the features retrieved from the database
// instead of requesting them once per style inside a layer
'cache-features': true
'cache-features': true,
// Require metrics to the renderer
metrics: false
},
http: {
timeout: 2000, // the timeout in ms for a http tile request

View File

@@ -202,7 +202,10 @@ var config = {
// If enabled Mapnik will reuse the features retrieved from the database
// instead of requesting them once per style inside a layer
'cache-features': true
'cache-features': true,
// Require metrics to the renderer
metrics: false
},
http: {

View File

@@ -202,7 +202,10 @@ var config = {
// If enabled Mapnik will reuse the features retrieved from the database
// instead of requesting them once per style inside a layer
'cache-features': true
'cache-features': true,
// Require metrics to the renderer
metrics: false
},
http: {

View File

@@ -201,8 +201,10 @@ var config = {
// If enabled Mapnik will reuse the features retrieved from the database
// instead of requesting them once per style inside a layer
'cache-features': true
'cache-features': true,
// Require metrics to the renderer
metrics: false
},
http: {
timeout: 2000, // the timeout in ms for a http tile request

View File

@@ -1,5 +1,4 @@
var assert = require('assert');
var step = require('step');
var _ = require('underscore'); // AUTH_FALLBACK
/**
*
@@ -47,39 +46,113 @@ AuthApi.prototype.authorizedBySigner = function(res, callback) {
});
};
function isValidApiKey(apikey) {
return apikey.type &&
apikey.user &&
apikey.databasePassword &&
apikey.databaseRole;
}
// Check if a request is authorized by api_key
//
// @param user
// @param req express request object
// @param res express response object
// @param callback function(err, authorized)
// NOTE: authorized is expected to be 0 or 1 (integer)
//
AuthApi.prototype.authorizedByAPIKey = function(user, req, callback) {
var givenKey = req.query.api_key || req.query.map_key;
if ( ! givenKey && req.body ) {
// check also in request body
givenKey = req.body.api_key || req.body.map_key;
}
if ( ! givenKey ) {
return callback(null, 0); // no api key, no authorization...
AuthApi.prototype.authorizedByAPIKey = function(user, res, callback) {
const apikeyToken = res.locals.api_key;
const basicAuthUsername = res.locals.basicAuthUsername;
if ( ! apikeyToken ) {
return callback(null, false); // no api key, no authorization...
}
var self = this;
this.metadataBackend.getApikey(user, apikeyToken, (err, apikey) => {
if (err) {
if (isNameNotFoundError(err)) {
err.http_status = 404;
}
step(
function () {
self.metadataBackend.getUserMapKey(user, this);
},
function checkApiKey(err, val){
assert.ifError(err);
return val && givenKey === val;
},
function finish(err, authorized) {
callback(err, authorized);
return callback(err);
}
);
//Remove this block when Auth fallback is not used anymore
// AUTH_FALLBACK
apikey.databaseRole = composeUserDatabase(apikey);
apikey.databasePassword = composeDatabasePassword(apikey);
if ( !isValidApiKey(apikey)) {
const error = new Error('Unauthorized');
error.type = 'auth';
error.subtype = 'api-key-not-found';
error.http_status = 401;
return callback(error);
}
if (!usernameMatches(basicAuthUsername, res.locals.user)) {
const error = new Error('Forbidden');
error.type = 'auth';
error.subtype = 'api-key-username-mismatch';
error.http_status = 403;
return callback(error);
}
if (!apikey.grantsMaps) {
const error = new Error('Forbidden');
error.type = 'auth';
error.subtype = 'api-key-does-not-grant-access';
error.http_status = 403;
return callback(error);
}
return callback(null, true);
});
};
//Remove this block when Auth fallback is not used anymore
// AUTH_FALLBACK
function composeUserDatabase (apikey) {
if (shouldComposeUserDatabase(apikey)) {
return _.template(global.environment.postgres_auth_user, apikey);
}
return apikey.databaseRole;
}
//Remove this block when Auth fallback is not used anymore
// AUTH_FALLBACK
function composeDatabasePassword (apikey) {
if (shouldComposeDatabasePassword(apikey)) {
return global.environment.postgres.password;
}
return apikey.databasePassword;
}
//Remove this block when Auth fallback is not used anymore
// AUTH_FALLBACK
function shouldComposeDatabasePassword (apikey) {
return !apikey.databasePassword && global.environment.postgres.password;
}
//Remove this block when Auth fallback is not used anymore
// AUTH_FALLBACK
function shouldComposeUserDatabase(apikey) {
return !apikey.databaseRole && apikey.user_id && global.environment.postgres_auth_user;
}
function isNameNotFoundError (err) {
return err.message && -1 !== err.message.indexOf('name not found');
}
function usernameMatches (basicAuthUsername, requestUsername) {
return !(basicAuthUsername && (basicAuthUsername !== requestUsername));
}
/**
* Check access authorization
*
@@ -88,51 +161,57 @@ AuthApi.prototype.authorizedByAPIKey = function(user, req, callback) {
* @param callback function(err, allowed) is access allowed not?
*/
AuthApi.prototype.authorize = function(req, res, callback) {
var self = this;
var user = res.locals.user;
step(
function () {
self.authorizedByAPIKey(user, req, this);
},
function checkApiKey(err, authorized){
req.profiler.done('authorizedByAPIKey');
assert.ifError(err);
this.authorizedByAPIKey(user, res, (err, isAuthorizedByApikey) => {
if (err) {
return callback(err);
}
// if not authorized by api_key, continue
if (!authorized) {
// not authorized by api_key, check if authorized by signer
return self.authorizedBySigner(res, this);
}
if (isAuthorizedByApikey) {
return this.pgConnection.setDBAuth(user, res.locals, 'regular', function (err) {
req.profiler.done('setDBAuth');
// authorized by api key, login as the given username and stop
self.pgConnection.setDBAuth(user, res.locals, function(err) {
callback(err, true); // authorized (or error)
if (err) {
return callback(err);
}
callback(null, true);
});
},
function checkSignAuthorized(err, authorized) {
}
this.authorizedBySigner(res, (err, isAuthorizedBySigner) => {
if (err) {
return callback(err);
}
if ( ! authorized ) {
// request not authorized by signer.
if (isAuthorizedBySigner) {
return this.pgConnection.setDBAuth(user, res.locals, 'master', function (err) {
req.profiler.done('setDBAuth');
// if no signer name was given, let dbparams and
// PostgreSQL do the rest.
//
if ( ! res.locals.signer ) {
return callback(null, true); // authorized so far
}
if (err) {
return callback(err);
}
// if signer name was given, return no authorization
return callback(null, false);
callback(null, true);
});
}
self.pgConnection.setDBAuth(user, res.locals, function(err) {
req.profiler.done('setDBAuth');
callback(err, true); // authorized (or error)
});
}
);
// if no signer name was given, use default api key
if (!res.locals.signer) {
return this.pgConnection.setDBAuth(user, res.locals, 'default', function (err) {
req.profiler.done('setDBAuth');
if (err) {
return callback(err);
}
callback(null, true);
});
}
// if signer name was given, return no authorization
return callback(null, false);
});
});
};

View File

@@ -1,7 +1,6 @@
var assert = require('assert');
var step = require('step');
var PSQL = require('cartodb-psql');
var _ = require('underscore');
const debug = require('debug')('cachechan');
function PgConnection(metadataBackend) {
this.metadataBackend = metadataBackend;
@@ -20,45 +19,85 @@ module.exports = PgConnection;
//
// @param callback function(err)
//
PgConnection.prototype.setDBAuth = function(username, params, callback) {
var self = this;
var user_params = {};
var auth_user = global.environment.postgres_auth_user;
var auth_pass = global.environment.postgres_auth_pass;
step(
function getId() {
self.metadataBackend.getUserId(username, this);
},
function(err, user_id) {
assert.ifError(err);
user_params.user_id = user_id;
var dbuser = _.template(auth_user, user_params);
_.extend(params, {dbuser:dbuser});
// skip looking up user_password if postgres_auth_pass
// doesn't contain the "user_password" label
if (!auth_pass || ! auth_pass.match(/\buser_password\b/) ) {
return null;
PgConnection.prototype.setDBAuth = function(username, params, apikeyType, callback) {
if (apikeyType === 'master') {
this.metadataBackend.getMasterApikey(username, (err, apikey) => {
if (err) {
if (isNameNotFoundError(err)) {
err.http_status = 404;
}
return callback(err);
}
self.metadataBackend.getUserDBPass(username, this);
},
function(err, user_password) {
assert.ifError(err);
user_params.user_password = user_password;
if ( auth_pass ) {
var dbpass = _.template(auth_pass, user_params);
_.extend(params, {dbpassword:dbpass});
params.dbuser = apikey.databaseRole;
params.dbpassword = apikey.databasePassword;
//Remove this block when Auth fallback is not used anymore
// AUTH_FALLBACK
if (!params.dbuser && apikey.user_id && global.environment.postgres_auth_user) {
params.dbuser = _.template(global.environment.postgres_auth_user, apikey);
}
return true;
},
function finish(err) {
callback(err);
}
);
return callback();
});
} else if (apikeyType === 'regular') { //Actually it can be any type of api key
this.metadataBackend.getApikey(username, params.api_key, (err, apikey) => {
if (err) {
if (isNameNotFoundError(err)) {
err.http_status = 404;
}
return callback(err);
}
params.dbuser = apikey.databaseRole;
params.dbpassword = apikey.databasePassword;
//Remove this block when Auth fallback is not used anymore
// AUTH_FALLBACK
// master apikey has been recreated from user's metadata
if (!params.dbuser && apikey.user_id && apikey.type === 'master' && global.environment.postgres_auth_user) {
params.dbuser = _.template(global.environment.postgres_auth_user, apikey);
}
//Remove this block when Auth fallback is not used anymore
// AUTH_FALLBACK
// default apikey has been recreated from user's metadata
if (!params.dbpassword && global.environment.postgres.password) {
params.dbpassword = global.environment.postgres.password;
}
return callback();
});
} else if (apikeyType === 'default') {
this.metadataBackend.getApikey(username, 'default_public', (err, apikey) => {
if (err) {
if (isNameNotFoundError(err)) {
err.http_status = 404;
}
return callback(err);
}
params.dbuser = apikey.databaseRole;
params.dbpassword = apikey.databasePassword;
//Remove this block when Auth fallback is not used anymore
// AUTH_FALLBACK
if (!params.dbpassword && global.environment.postgres.password) {
params.dbpassword = global.environment.postgres.password;
}
return callback();
});
} else {
return callback(new Error(`Invalid Apikey type: ${apikeyType}, valid ones: master, regular, default`));
}
};
function isNameNotFoundError (err) {
return err.message && -1 !== err.message.indexOf('name not found');
}
// Set db connection parameters to those for the given username
//
// @param dbowner cartodb username of database owner,
@@ -71,36 +110,30 @@ PgConnection.prototype.setDBAuth = function(username, params, callback) {
// @param callback function(err)
//
PgConnection.prototype.setDBConn = function(dbowner, params, callback) {
var self = this;
// Add default database connection parameters
// if none given
_.defaults(params, {
dbuser: global.environment.postgres.user,
dbpassword: global.environment.postgres.password,
// dbuser: global.environment.postgres.user,
// dbpassword: global.environment.postgres.password,
dbhost: global.environment.postgres.host,
dbport: global.environment.postgres.port
});
step(
function getConnectionParams() {
self.metadataBackend.getUserDBConnectionParams(dbowner, this);
},
function extendParams(err, dbParams){
assert.ifError(err);
// we don't want null values or overwrite a non public user
if (params.dbuser !== 'publicuser' || !dbParams.dbuser) {
delete dbParams.dbuser;
}
if ( dbParams ) {
_.extend(params, dbParams);
}
return null;
},
function finish(err) {
callback(err);
}
);
};
this.metadataBackend.getUserDBConnectionParams(dbowner, (err, dbParams) => {
if (err) {
return callback(err);
}
// we dont want null values or overwrite a non public user
if (params.dbuser !== 'publicuser' || !dbParams.dbuser) {
delete dbParams.dbuser;
}
if (dbParams) {
_.extend(params, dbParams);
}
callback();
});
};
/**
* Returns a `cartodb-psql` object for a given username.
@@ -109,28 +142,37 @@ PgConnection.prototype.setDBConn = function(dbowner, params, callback) {
*/
PgConnection.prototype.getConnection = function(username, callback) {
var self = this;
debug("getConn1");
var params = {};
require('debug')('cachechan')("getConn1");
step(
function setAuth() {
self.setDBAuth(username, params, this);
},
function setConn(err) {
assert.ifError(err);
self.setDBConn(username, params, this);
},
function openConnection(err) {
assert.ifError(err);
return callback(err, new PSQL({
user: params.dbuser,
pass: params.dbpass,
host: params.dbhost,
port: params.dbport,
dbname: params.dbname
}));
this.getDatabaseParams(username, (err, databaseParams) => {
if (err) {
return callback(err);
}
);
return callback(err, new PSQL({
user: databaseParams.dbuser,
pass: databaseParams.dbpass,
host: databaseParams.dbhost,
port: databaseParams.dbport,
dbname: databaseParams.dbname
}));
});
};
PgConnection.prototype.getDatabaseParams = function(username, callback) {
const databaseParams = {};
this.setDBAuth(username, databaseParams, 'master', err => {
if (err) {
return callback(err);
}
this.setDBConn(username, databaseParams, err => {
if (err) {
return callback(err);
}
callback(null, databaseParams);
});
});
};

View File

@@ -1,6 +1,4 @@
var assert = require('assert');
var PSQL = require('cartodb-psql');
var step = require('step');
function PgQueryRunner(pgConnection) {
this.pgConnection = pgConnection;
@@ -16,31 +14,23 @@ module.exports = PgQueryRunner;
* @param {Function} callback function({Error}, {Array}) second argument is guaranteed to be an array
*/
PgQueryRunner.prototype.run = function(username, query, callback) {
var self = this;
var params = {};
step(
function setAuth() {
self.pgConnection.setDBAuth(username, params, this);
},
function setConn(err) {
assert.ifError(err);
self.pgConnection.setDBConn(username, params, this);
},
function executeQuery(err) {
assert.ifError(err);
var psql = new PSQL({
user: params.dbuser,
pass: params.dbpass,
host: params.dbhost,
port: params.dbport,
dbname: params.dbname
});
psql.query(query, function(err, resultSet) {
resultSet = resultSet || {};
return callback(err, resultSet.rows || []);
});
this.pgConnection.getDatabaseParams(username, (err, databaseParams) => {
if (err) {
return callback(err);
}
);
const psql = new PSQL({
user: databaseParams.dbuser,
pass: databaseParams.dbpass,
host: databaseParams.dbhost,
port: databaseParams.dbport,
dbname: databaseParams.dbname
});
psql.query(query, function (err, resultSet) {
resultSet = resultSet || {};
return callback(err, resultSet.rows || []);
});
});
};

View File

@@ -12,26 +12,26 @@ AnalysesController.prototype.register = function (app) {
app.get(
`${app.base_url_mapconfig}/analyses/catalog`,
cors(),
userMiddleware,
userMiddleware(),
this.prepareContext,
this.createPGClient(),
this.getDataFromQuery({ queryTemplate: catalogQueryTpl, key: 'catalog' }),
this.getDataFromQuery({ queryTemplate: tablesQueryTpl, key: 'tables' }),
this.prepareResponse(),
this.setCacheControlHeader(),
this.sendResponse(),
this.unathorizedError()
createPGClient(),
getDataFromQuery({ queryTemplate: catalogQueryTpl, key: 'catalog' }),
getDataFromQuery({ queryTemplate: tablesQueryTpl, key: 'tables' }),
prepareResponse(),
setCacheControlHeader(),
sendResponse(),
unauthorizedError()
);
};
AnalysesController.prototype.createPGClient = function () {
function createPGClient () {
return function createPGClientMiddleware (req, res, next) {
res.locals.pg = new PSQL(dbParamsFromReqParams(res.locals));
next();
};
};
}
AnalysesController.prototype.getDataFromQuery = function ({ queryTemplate, key }) {
function getDataFromQuery({ queryTemplate, key }) {
const readOnlyTransactionOn = true;
return function getCatalogMiddleware(req, res, next) {
@@ -48,9 +48,9 @@ AnalysesController.prototype.getDataFromQuery = function ({ queryTemplate, key }
next();
}, readOnlyTransactionOn);
};
};
}
AnalysesController.prototype.prepareResponse = function () {
function prepareResponse () {
return function prepareResponseMiddleware (req, res, next) {
const { catalog, tables } = res.locals;
@@ -91,16 +91,16 @@ AnalysesController.prototype.prepareResponse = function () {
next();
};
};
}
AnalysesController.prototype.setCacheControlHeader = function () {
function setCacheControlHeader () {
return function setCacheControlHeaderMiddleware (req, res, next) {
res.set('Cache-Control', 'public,max-age=10,must-revalidate');
next();
};
};
}
AnalysesController.prototype.sendResponse = function() {
function sendResponse () {
return function sendResponseMiddleware (req, res) {
res.status(200);
@@ -110,9 +110,9 @@ AnalysesController.prototype.sendResponse = function() {
res.json(res.body);
}
};
};
}
AnalysesController.prototype.unathorizedError = function () {
function unauthorizedError () {
return function unathorizedErrorMiddleware(err, req, res, next) {
if (err.message.match(/permission\sdenied/)) {
err = new Error('Unauthorized');
@@ -121,7 +121,7 @@ AnalysesController.prototype.unathorizedError = function () {
next(err);
};
};
}
const catalogQueryTpl = ctx => `
SELECT analysis_def->>'type' as type, * FROM cdb_analysis_catalog WHERE username = '${ctx._username}'

View File

@@ -1,20 +1,22 @@
var assert = require('assert');
var step = require('step');
var cors = require('../middleware/cors');
var userMiddleware = require('../middleware/user');
var allowQueryParams = require('../middleware/allow-query-params');
var vectorError = require('../middleware/vector-error');
var DataviewBackend = require('../backends/dataview');
var AnalysisStatusBackend = require('../backends/analysis-status');
var MapStoreMapConfigProvider = require('../models/mapconfig/provider/map-store-provider');
var QueryTables = require('cartodb-query-tables');
const cors = require('../middleware/cors');
const userMiddleware = require('../middleware/user');
const allowQueryParams = require('../middleware/allow-query-params');
const vectorError = require('../middleware/vector-error');
const DataviewBackend = require('../backends/dataview');
const AnalysisStatusBackend = require('../backends/analysis-status');
const MapStoreMapConfigProvider = require('../models/mapconfig/provider/map-store-provider');
const QueryTables = require('cartodb-query-tables');
const SUPPORTED_FORMATS = {
grid_json: true,
json_torque: true,
torque_json: true,
png: true,
png32: true,
mvt: true
};
/**
* @param {AuthApi} authApi
* @param {prepareContext} prepareContext
* @param {PgConnection} pgConnection
* @param {MapStore} mapStore
* @param {TileBackend} tileBackend
@@ -46,64 +48,119 @@ function LayergroupController(prepareContext, pgConnection, mapStore, tileBacken
module.exports = LayergroupController;
LayergroupController.prototype.register = function(app) {
const { base_url_mapconfig: basePath } = app;
app.get(
app.base_url_mapconfig + '/:token/:z/:x/:y@:scale_factor?x.:format',
`${basePath}/:token/:z/:x/:y@:scale_factor?x.:format`,
cors(),
userMiddleware,
userMiddleware(),
this.prepareContext,
this.tile.bind(this),
createMapStoreMapConfigProvider(this.mapStore, this.userLimitsApi),
getTile(this.tileBackend, 'map_tile'),
setCacheControlHeader(),
setLastModifiedHeader(),
getAffectedTables(this.layergroupAffectedTables, this.pgConnection, this.mapStore),
setCacheChannelHeader(),
setSurrogateKeyHeader(this.surrogateKeysCache),
incrementSuccessMetrics(global.statsClient),
sendResponse(),
incrementErrorMetrics(global.statsClient),
tileError(),
vectorError()
);
app.get(
app.base_url_mapconfig + '/:token/:z/:x/:y.:format',
`${basePath}/:token/:z/:x/:y.:format`,
cors(),
userMiddleware,
userMiddleware(),
this.prepareContext,
this.tile.bind(this),
createMapStoreMapConfigProvider(this.mapStore, this.userLimitsApi),
getTile(this.tileBackend, 'map_tile'),
setCacheControlHeader(),
setLastModifiedHeader(),
getAffectedTables(this.layergroupAffectedTables, this.pgConnection, this.mapStore),
setCacheChannelHeader(),
setSurrogateKeyHeader(this.surrogateKeysCache),
incrementSuccessMetrics(global.statsClient),
sendResponse(),
incrementErrorMetrics(global.statsClient),
tileError(),
vectorError()
);
app.get(
app.base_url_mapconfig + '/:token/:layer/:z/:x/:y.(:format)',
`${basePath}/:token/:layer/:z/:x/:y.(:format)`,
distinguishLayergroupFromStaticRoute(),
cors(),
userMiddleware,
validateLayerRouteMiddleware,
userMiddleware(),
this.prepareContext,
this.layer.bind(this),
createMapStoreMapConfigProvider(this.mapStore, this.userLimitsApi),
getTile(this.tileBackend, 'maplayer_tile'),
setCacheControlHeader(),
setLastModifiedHeader(),
getAffectedTables(this.layergroupAffectedTables, this.pgConnection, this.mapStore),
setCacheChannelHeader(),
setSurrogateKeyHeader(this.surrogateKeysCache),
incrementSuccessMetrics(global.statsClient),
sendResponse(),
incrementErrorMetrics(global.statsClient),
tileError(),
vectorError()
);
app.get(
app.base_url_mapconfig + '/:token/:layer/attributes/:fid',
`${basePath}/:token/:layer/attributes/:fid`,
cors(),
userMiddleware,
userMiddleware(),
this.prepareContext,
this.attributes.bind(this)
createMapStoreMapConfigProvider(this.mapStore, this.userLimitsApi),
getFeatureAttributes(this.attributesBackend),
setCacheControlHeader(),
setLastModifiedHeader(),
getAffectedTables(this.layergroupAffectedTables, this.pgConnection, this.mapStore),
setCacheChannelHeader(),
setSurrogateKeyHeader(this.surrogateKeysCache),
sendResponse()
);
const forcedFormat = 'png';
app.get(
app.base_url_mapconfig + '/static/center/:token/:z/:lat/:lng/:width/:height.:format',
`${basePath}/static/center/:token/:z/:lat/:lng/:width/:height.:format`,
cors(),
userMiddleware,
userMiddleware(),
allowQueryParams(['layer']),
this.prepareContext,
this.center.bind(this)
createMapStoreMapConfigProvider(this.mapStore, this.userLimitsApi, forcedFormat),
getPreviewImageByCenter(this.previewBackend),
setCacheControlHeader(),
setLastModifiedHeader(),
getAffectedTables(this.layergroupAffectedTables, this.pgConnection, this.mapStore),
setCacheChannelHeader(),
setSurrogateKeyHeader(this.surrogateKeysCache),
sendResponse()
);
app.get(
app.base_url_mapconfig + '/static/bbox/:token/:west,:south,:east,:north/:width/:height.:format',
`${basePath}/static/bbox/:token/:west,:south,:east,:north/:width/:height.:format`,
cors(),
userMiddleware,
userMiddleware(),
allowQueryParams(['layer']),
this.prepareContext,
this.bbox.bind(this)
createMapStoreMapConfigProvider(this.mapStore, this.userLimitsApi, forcedFormat),
getPreviewImageByBoundingBox(this.previewBackend),
setCacheControlHeader(),
setLastModifiedHeader(),
getAffectedTables(this.layergroupAffectedTables, this.pgConnection, this.mapStore),
setCacheChannelHeader(),
setSurrogateKeyHeader(this.surrogateKeysCache),
sendResponse()
);
// Undocumented/non-supported API endpoint methods.
// Use at your own peril.
var allowedDataviewQueryParams = [
const allowedDataviewQueryParams = [
'filters', // json
'own_filter', // 0, 1
'no_filters', // 0, 1
@@ -119,395 +176,465 @@ LayergroupController.prototype.register = function(app) {
];
app.get(
app.base_url_mapconfig + '/:token/dataview/:dataviewName',
`${basePath}/:token/dataview/:dataviewName`,
cors(),
userMiddleware,
userMiddleware(),
allowQueryParams(allowedDataviewQueryParams),
this.prepareContext,
this.dataview.bind(this)
createMapStoreMapConfigProvider(this.mapStore, this.userLimitsApi),
getDataview(this.dataviewBackend),
setCacheControlHeader(),
setLastModifiedHeader(),
getAffectedTables(this.layergroupAffectedTables, this.pgConnection, this.mapStore),
setCacheChannelHeader(),
setSurrogateKeyHeader(this.surrogateKeysCache),
sendResponse()
);
app.get(
app.base_url_mapconfig + '/:token/:layer/widget/:dataviewName',
`${basePath}/:token/:layer/widget/:dataviewName`,
cors(),
userMiddleware,
userMiddleware(),
allowQueryParams(allowedDataviewQueryParams),
this.prepareContext,
this.dataview.bind(this)
createMapStoreMapConfigProvider(this.mapStore, this.userLimitsApi),
getDataview(this.dataviewBackend),
setCacheControlHeader(),
setLastModifiedHeader(),
getAffectedTables(this.layergroupAffectedTables, this.pgConnection, this.mapStore),
setCacheChannelHeader(),
setSurrogateKeyHeader(this.surrogateKeysCache),
sendResponse()
);
app.get(
app.base_url_mapconfig + '/:token/dataview/:dataviewName/search',
`${basePath}/:token/dataview/:dataviewName/search`,
cors(),
userMiddleware,
userMiddleware(),
allowQueryParams(allowedDataviewQueryParams),
this.prepareContext,
this.dataviewSearch.bind(this)
createMapStoreMapConfigProvider(this.mapStore, this.userLimitsApi),
dataviewSearch(this.dataviewBackend),
setCacheControlHeader(),
setLastModifiedHeader(),
getAffectedTables(this.layergroupAffectedTables, this.pgConnection, this.mapStore),
setCacheChannelHeader(),
setSurrogateKeyHeader(this.surrogateKeysCache),
sendResponse()
);
app.get(
app.base_url_mapconfig + '/:token/:layer/widget/:dataviewName/search',
`${basePath}/:token/:layer/widget/:dataviewName/search`,
cors(),
userMiddleware,
userMiddleware(),
allowQueryParams(allowedDataviewQueryParams),
this.prepareContext,
this.dataviewSearch.bind(this)
createMapStoreMapConfigProvider(this.mapStore, this.userLimitsApi),
dataviewSearch(this.dataviewBackend),
setCacheControlHeader(),
setLastModifiedHeader(),
getAffectedTables(this.layergroupAffectedTables, this.pgConnection, this.mapStore),
setCacheChannelHeader(),
setSurrogateKeyHeader(this.surrogateKeysCache),
sendResponse()
);
app.get(
app.base_url_mapconfig + '/:token/analysis/node/:nodeId',
`${basePath}/:token/analysis/node/:nodeId`,
cors(),
userMiddleware,
userMiddleware(),
this.prepareContext,
this.analysisNodeStatus.bind(this)
analysisNodeStatus(this.analysisStatusBackend),
sendResponse()
);
};
LayergroupController.prototype.analysisNodeStatus = function(req, res, next) {
var self = this;
function distinguishLayergroupFromStaticRoute () {
return function distinguishLayergroupFromStaticRouteMiddleware(req, res, next) {
if (req.params.token === 'static') {
return next('route');
}
step(
function retrieveNodeStatus() {
self.analysisStatusBackend.getNodeStatus(res.locals, this);
},
function finish(err, nodeStatus, stats) {
req.profiler.add(stats || {});
next();
};
}
function analysisNodeStatus (analysisStatusBackend) {
return function analysisNodeStatusMiddleware(req, res, next) {
analysisStatusBackend.getNodeStatus(res.locals, (err, nodeStatus, stats = {}) => {
req.profiler.add(stats);
if (err) {
err.label = 'GET NODE STATUS';
next(err);
} else {
self.sendResponse(req, res, nodeStatus, 200, {
'Cache-Control': 'public,max-age=5',
'Last-Modified': new Date().toUTCString()
});
return next(err);
}
res.set({
'Cache-Control': 'public,max-age=5',
'Last-Modified': new Date().toUTCString()
});
res.body = nodeStatus;
next();
});
};
}
function getRequestParams(locals) {
const params = Object.assign({}, locals);
delete params.mapConfigProvider;
delete params.allowedQueryParams;
return params;
}
function createMapStoreMapConfigProvider (mapStore, userLimitsApi, forcedFormat = null) {
return function createMapStoreMapConfigProviderMiddleware (req, res, next) {
const { user } = res.locals;
const params = getRequestParams(res.locals);
if (forcedFormat) {
params.format = forcedFormat;
params.layer = params.layer || 'all';
}
);
};
LayergroupController.prototype.dataview = function(req, res, next) {
var self = this;
res.locals.mapConfigProvider = new MapStoreMapConfigProvider(mapStore, user, userLimitsApi, params);
step(
function retrieveDataview() {
var mapConfigProvider = new MapStoreMapConfigProvider(
self.mapStore, res.locals.user, self.userLimitsApi, res.locals
);
self.dataviewBackend.getDataview(
mapConfigProvider,
res.locals.user,
res.locals,
this
);
},
function finish(err, dataview, stats) {
req.profiler.add(stats || {});
next();
};
}
function getDataview (dataviewBackend) {
return function getDataviewMiddleware (req, res, next) {
const { user, mapConfigProvider } = res.locals;
const params = getRequestParams(res.locals);
dataviewBackend.getDataview(mapConfigProvider, user, params, (err, dataview, stats = {}) => {
req.profiler.add(stats);
if (err) {
err.label = 'GET DATAVIEW';
next(err);
} else {
self.sendResponse(req, res, dataview, 200);
return next(err);
}
}
);
};
LayergroupController.prototype.dataviewSearch = function(req, res, next) {
var self = this;
res.body = dataview;
step(
function searchDataview() {
var mapConfigProvider = new MapStoreMapConfigProvider(
self.mapStore, res.locals.user, self.userLimitsApi, res.locals
);
self.dataviewBackend.search(mapConfigProvider, res.locals.user, req.params.dataviewName, res.locals, this);
},
function finish(err, searchResult, stats) {
req.profiler.add(stats || {});
next();
});
};
}
function dataviewSearch (dataviewBackend) {
return function dataviewSearchMiddleware (req, res, next) {
const { user, dataviewName, mapConfigProvider } = res.locals;
const params = getRequestParams(res.locals);
dataviewBackend.search(mapConfigProvider, user, dataviewName, params, (err, searchResult, stats = {}) => {
req.profiler.add(stats);
if (err) {
err.label = 'GET DATAVIEW SEARCH';
next(err);
} else {
self.sendResponse(req, res, searchResult, 200);
return next(err);
}
}
);
};
res.body = searchResult;
LayergroupController.prototype.attributes = function(req, res, next) {
var self = this;
next();
});
};
}
req.profiler.start('windshaft.maplayer_attribute');
function getFeatureAttributes (attributesBackend) {
return function getFeatureAttributesMiddleware (req, res, next) {
req.profiler.start('windshaft.maplayer_attribute');
step(
function retrieveFeatureAttributes() {
var mapConfigProvider = new MapStoreMapConfigProvider(
self.mapStore, res.locals.user, self.userLimitsApi, res.locals
);
self.attributesBackend.getFeatureAttributes(mapConfigProvider, res.locals, false, this);
},
function finish(err, tile, stats) {
req.profiler.add(stats || {});
const { mapConfigProvider } = res.locals;
const params = getRequestParams(res.locals);
attributesBackend.getFeatureAttributes(mapConfigProvider, params, false, (err, tile, stats = {}) => {
req.profiler.add(stats);
if (err) {
err.label = 'GET ATTRIBUTES';
next(err);
} else {
self.sendResponse(req, res, tile, 200);
return next(err);
}
}
);
};
res.body = tile;
// Gets a tile for a given token and set of tile ZXY coords. (OSM style)
LayergroupController.prototype.tile = function(req, res, next) {
req.profiler.start('windshaft.map_tile');
this.tileOrLayer(req, res, next);
};
// Gets a tile for a given token, layer set of tile ZXY coords. (OSM style)
LayergroupController.prototype.layer = function(req, res, next) {
req.profiler.start('windshaft.maplayer_tile');
this.tileOrLayer(req, res, next);
};
LayergroupController.prototype.tileOrLayer = function (req, res, next) {
var self = this;
step(
function mapController$getTileOrGrid() {
self.tileBackend.getTile(
new MapStoreMapConfigProvider(self.mapStore, res.locals.user, self.userLimitsApi, res.locals),
req.params, this
);
},
function mapController$finalize(err, tile, headers, stats) {
req.profiler.add(stats);
self.finalizeGetTileOrGrid(err, req, res, tile, headers, next);
}
);
};
function getStatusCode(tile, format){
return tile.length===0 && format==='mvt'? 204:200;
next();
});
};
}
// This function is meant for being called as the very last
// step by all endpoints serving tiles or grids
LayergroupController.prototype.finalizeGetTileOrGrid = function(err, req, res, tile, headers, next) {
var supportedFormats = {
grid_json: true,
json_torque: true,
torque_json: true,
png: true,
png32: true,
mvt: true
};
function getStatusCode(tile, format){
return tile.length === 0 && format === 'mvt'? 204 : 200;
}
var formatStat = 'invalid';
if (req.params.format) {
var format = req.params.format.replace('.', '_');
if (supportedFormats[format]) {
formatStat = format;
}
}
function parseFormat (format = '') {
const prettyFormat = format.replace('.', '_');
return SUPPORTED_FORMATS[prettyFormat] ? prettyFormat : 'invalid';
}
if (err) {
// See https://github.com/Vizzuality/Windshaft-cartodb/issues/68
var errMsg = err.message ? ( '' + err.message ) : ( '' + err );
function getTile (tileBackend, profileLabel = 'tile') {
return function getTileMiddleware (req, res, next) {
req.profiler.start(`windshaft.${profileLabel}`);
// Rewrite mapnik parsing errors to start with layer number
var matches = errMsg.match("(.*) in style 'layer([0-9]+)'");
if (matches) {
errMsg = 'style'+matches[2]+': ' + matches[1];
}
err.message = errMsg;
const { mapConfigProvider } = res.locals;
const params = getRequestParams(res.locals);
err.label = 'TILE RENDER';
next(err);
global.statsClient.increment('windshaft.tiles.error');
global.statsClient.increment('windshaft.tiles.' + formatStat + '.error');
} else {
this.sendResponse(req, res, tile, getStatusCode(tile, formatStat), headers);
global.statsClient.increment('windshaft.tiles.success');
global.statsClient.increment('windshaft.tiles.' + formatStat + '.success');
}
};
LayergroupController.prototype.bbox = function(req, res, next) {
this.staticMap(req, res, +req.params.width, +req.params.height, {
west: +req.params.west,
north: +req.params.north,
east: +req.params.east,
south: +req.params.south
}, null, next);
};
LayergroupController.prototype.center = function(req, res, next) {
this.staticMap(req, res, +req.params.width, +req.params.height, +req.params.z, {
lng: +req.params.lng,
lat: +req.params.lat
}, next);
};
LayergroupController.prototype.staticMap = function(req, res, width, height, zoom /* bounds */, center, next) {
var format = req.params.format === 'jpg' ? 'jpeg' : 'png';
// We force always the tile to be generated using PNG because
// is the only format we support by now
res.locals.format = 'png';
res.locals.layer = res.locals.layer || 'all';
var self = this;
step(
function getImage() {
if (center) {
self.previewBackend.getImage(
new MapStoreMapConfigProvider(self.mapStore, res.locals.user, self.userLimitsApi, res.locals),
format, width, height, zoom, center, this);
} else {
self.previewBackend.getImage(
new MapStoreMapConfigProvider(self.mapStore, res.locals.user, self.userLimitsApi, res.locals),
format, width, height, zoom /* bounds */, this);
}
},
function handleImage(err, image, headers, stats) {
req.profiler.done('render-' + format);
req.profiler.add(stats || {});
tileBackend.getTile(mapConfigProvider, params, (err, tile, headers, stats = {}) => {
req.profiler.add(stats);
if (err) {
err.label = 'STATIC_MAP';
next(err);
} else {
res.set('Content-Type', headers['Content-Type'] || 'image/' + format);
self.sendResponse(req, res, image, 200);
}
}
);
};
LayergroupController.prototype.sendResponse = function(req, res, body, status, headers) {
var self = this;
req.profiler.done('res');
res.set('Cache-Control', 'public,max-age=31536000');
// Set Last-Modified header
var lastUpdated;
if (res.locals.cache_buster) {
// Assuming cache_buster is a timestamp
lastUpdated = new Date(parseInt(res.locals.cache_buster));
} else {
lastUpdated = new Date();
}
res.set('Last-Modified', lastUpdated.toUTCString());
var dbName = res.locals.dbname;
step(
function getAffectedTables() {
self.getAffectedTables(res.locals.user, dbName, res.locals.token, this);
},
function sendResponse(err, affectedTables) {
req.profiler.done('affectedTables');
if (err) {
global.logger.warn('ERROR generating cache channel: ' + err);
}
if (!!affectedTables) {
res.set('X-Cache-Channel', affectedTables.getCacheChannel());
self.surrogateKeysCache.tag(res, affectedTables);
return next(err);
}
if (headers) {
res.set(headers);
}
res.status(status);
const formatStat = parseFormat(req.params.format);
if (!Buffer.isBuffer(body) && typeof body === 'object') {
if (req.query && req.query.callback) {
res.jsonp(body);
} else {
res.json(body);
}
} else {
res.send(body);
}
}
);
};
res.statusCode = getStatusCode(tile, formatStat);
res.body = tile;
LayergroupController.prototype.getAffectedTables = function(user, dbName, layergroupId, callback) {
if (this.layergroupAffectedTables.hasAffectedTables(dbName, layergroupId)) {
return callback(null, this.layergroupAffectedTables.get(dbName, layergroupId));
}
var self = this;
step(
function extractSQL() {
step(
function loadFromStore() {
self.mapStore.load(layergroupId, this);
},
function getSQL(err, mapConfig) {
assert.ifError(err);
var queries = [];
mapConfig.getLayers().forEach(function(layer) {
queries.push(layer.options.sql);
if (layer.options.affected_tables) {
layer.options.affected_tables.map(function(table) {
queries.push('SELECT * FROM ' + table + ' LIMIT 0');
});
}
});
return queries.length ? queries.join(';') : null;
},
this
);
},
function findAffectedTables(err, sql) {
assert.ifError(err);
if ( ! sql ) {
throw new Error("this request doesn't need an X-Cache-Channel generated");
}
step(
function getConnection() {
self.pgConnection.getConnection(user, this);
},
function getAffectedTables(err, connection) {
assert.ifError(err);
QueryTables.getAffectedTablesFromQuery(connection, sql, this);
},
this
);
},
function buildCacheChannel(err, tables) {
assert.ifError(err);
self.layergroupAffectedTables.set(dbName, layergroupId, tables);
return tables;
},
callback
);
};
function validateLayerRouteMiddleware(req, res, next) {
if (req.params.token === 'static') {
return next('route');
}
next();
next();
});
};
}
function getPreviewImageByCenter (previewBackend) {
return function getPreviewImageByCenterMiddleware (req, res, next) {
const width = +req.params.width;
const height = +req.params.height;
const zoom = +req.params.z;
const center = {
lng: +req.params.lng,
lat: +req.params.lat
};
const format = req.params.format === 'jpg' ? 'jpeg' : 'png';
const { mapConfigProvider: provider } = res.locals;
previewBackend.getImage(provider, format, width, height, zoom, center, (err, image, headers, stats = {}) => {
req.profiler.done(`render-${format}`);
req.profiler.add(stats);
if (err) {
err.label = 'STATIC_MAP';
return next(err);
}
if (headers) {
res.set(headers);
}
res.set('Content-Type', headers['Content-Type'] || `image/${format}`);
res.body = image;
next();
});
};
}
function getPreviewImageByBoundingBox (previewBackend) {
return function getPreviewImageByBoundingBoxMiddleware (req, res, next) {
const width = +req.params.width;
const height = +req.params.height;
const bounds = {
west: +req.params.west,
north: +req.params.north,
east: +req.params.east,
south: +req.params.south
};
const format = req.params.format === 'jpg' ? 'jpeg' : 'png';
const { mapConfigProvider: provider } = res.locals;
previewBackend.getImage(provider, format, width, height, bounds, (err, image, headers, stats = {}) => {
req.profiler.done(`render-${format}`);
req.profiler.add(stats);
if (err) {
err.label = 'STATIC_MAP';
return next(err);
}
if (headers) {
res.set(headers);
}
res.set('Content-Type', headers['Content-Type'] || `image/${format}`);
res.body = image;
next();
});
};
}
function setLastModifiedHeader () {
return function setLastModifiedHeaderMiddleware (req, res, next) {
let { cache_buster: cacheBuster } = res.locals;
cacheBuster = parseInt(cacheBuster, 10);
const lastUpdated = res.locals.cache_buster ? new Date(cacheBuster) : new Date();
res.set('Last-Modified', lastUpdated.toUTCString());
next();
};
}
function setCacheControlHeader () {
return function setCacheControlHeaderMiddleware (req, res, next) {
res.set('Cache-Control', 'public,max-age=31536000');
next();
};
}
function getAffectedTables (layergroupAffectedTables, pgConnection, mapStore) {
return function getAffectedTablesMiddleware (req, res, next) {
const { user, dbname, token } = res.locals;
if (layergroupAffectedTables.hasAffectedTables(dbname, token)) {
res.locals.affectedTables = layergroupAffectedTables.get(dbname, token);
return next();
}
mapStore.load(token, (err, mapconfig) => {
if (err) {
global.logger.warn('ERROR generating cache channel:', err);
return next();
}
const queries = [];
mapconfig.getLayers().forEach(function(layer) {
queries.push(layer.options.sql);
if (layer.options.affected_tables) {
layer.options.affected_tables.map(function(table) {
queries.push(`SELECT * FROM ${table} LIMIT 0`);
});
}
});
const sql = queries.length ? queries.join(';') : null;
if (!sql) {
global.logger.warn('ERROR generating cache channel:' +
' this request doesn\'t need an X-Cache-Channel generated');
return next();
}
pgConnection.getConnection(user, (err, connection) => {
if (err) {
global.logger.warn('ERROR generating cache channel:', err);
return next();
}
QueryTables.getAffectedTablesFromQuery(connection, sql, (err, affectedTables) => {
req.profiler.done('getAffectedTablesFromQuery');
if (err) {
global.logger.warn('ERROR generating cache channel: ', err);
return next();
}
// feed affected tables cache so it can be reused from, for instance, map controller
layergroupAffectedTables.set(dbname, token, affectedTables);
res.locals.affectedTables = affectedTables;
next();
});
});
});
};
}
function setCacheChannelHeader () {
return function setCacheChannelHeaderMiddleware (req, res, next) {
const { affectedTables } = res.locals;
if (affectedTables) {
res.set('X-Cache-Channel', affectedTables.getCacheChannel());
}
next();
};
}
function setSurrogateKeyHeader (surrogateKeysCache) {
return function setSurrogateKeyHeaderMiddleware (req, res, next) {
const { affectedTables } = res.locals;
if (affectedTables) {
surrogateKeysCache.tag(res, affectedTables);
}
next();
};
}
function incrementSuccessMetrics (statsClient) {
return function incrementSuccessMetricsMiddleware (req, res, next) {
const formatStat = parseFormat(req.params.format);
statsClient.increment('windshaft.tiles.success');
statsClient.increment(`windshaft.tiles.${formatStat}.success`);
next();
};
}
function sendResponse () {
return function sendResponseMiddleware (req, res) {
req.profiler.done('res');
res.status(res.statusCode || 200);
if (!Buffer.isBuffer(res.body) && typeof res.body === 'object') {
if (req.query && req.query.callback) {
res.jsonp(res.body);
} else {
res.json(res.body);
}
} else {
res.send(res.body);
}
};
}
function incrementErrorMetrics (statsClient) {
return function incrementErrorMetricsMiddleware (err, req, res, next) {
const formatStat = parseFormat(req.params.format);
statsClient.increment('windshaft.tiles.error');
statsClient.increment(`windshaft.tiles.${formatStat}.error`);
next(err);
};
}
function tileError () {
return function tileErrorMiddleware (err, req, res, next) {
// See https://github.com/Vizzuality/Windshaft-cartodb/issues/68
let errMsg = err.message ? ( '' + err.message ) : ( '' + err );
// Rewrite mapnik parsing errors to start with layer number
const matches = errMsg.match("(.*) in style 'layer([0-9]+)'");
if (matches) {
errMsg = `style${matches[2]}: ${matches[1]}`;
}
err.message = errMsg;
err.label = 'TILE RENDER';
next(err);
};
}

View File

@@ -1,20 +1,16 @@
var _ = require('underscore');
var windshaft = require('windshaft');
var QueryTables = require('cartodb-query-tables');
var ResourceLocator = require('../models/resource-locator');
var cors = require('../middleware/cors');
var userMiddleware = require('../middleware/user');
const _ = require('underscore');
const windshaft = require('windshaft');
const MapConfig = windshaft.model.MapConfig;
const Datasource = windshaft.model.Datasource;
const QueryTables = require('cartodb-query-tables');
const ResourceLocator = require('../models/resource-locator');
const cors = require('../middleware/cors');
const userMiddleware = require('../middleware/user');
const allowQueryParams = require('../middleware/allow-query-params');
var MapConfig = windshaft.model.MapConfig;
var Datasource = windshaft.model.Datasource;
var NamedMapsCacheEntry = require('../cache/model/named_maps_entry');
var NamedMapMapConfigProvider = require('../models/mapconfig/provider/named-map-provider');
var CreateLayergroupMapConfigProvider = require('../models/mapconfig/provider/create-layergroup-provider');
const NamedMapsCacheEntry = require('../cache/model/named_maps_entry');
const NamedMapMapConfigProvider = require('../models/mapconfig/provider/named-map-provider');
const CreateLayergroupMapConfigProvider = require('../models/mapconfig/provider/create-layergroup-provider');
const LayergroupMetadata = require('../utils/layergroup-metadata');
/**
* @param {AuthApi} authApi
@@ -41,7 +37,8 @@ function MapController(prepareContext, pgConnection, templateMaps, mapBackend, m
this.layergroupAffectedTables = layergroupAffectedTables;
this.mapConfigAdapter = mapConfigAdapter;
this.resourceLocator = new ResourceLocator(global.environment);
const resourceLocator = new ResourceLocator(global.environment);
this.layergroupMetadata = new LayergroupMetadata(resourceLocator);
this.statsBackend = statsBackend;
this.prepareContext = prepareContext;
@@ -69,35 +66,55 @@ MapController.prototype.composeCreateMapMiddleware = function (useTemplate = fal
return [
cors(),
userMiddleware,
userMiddleware(),
allowQueryParams(['aggregation']),
this.prepareContext,
this.initProfiler(isTemplateInstantiation),
this.checkJsonContentType(),
useTemplate ? this.checkInstantiteLayergroup() : this.checkCreateLayergroup(),
useTemplate ? this.getTemplate() : this.prepareAdapterMapConfig(),
useTemplate ? this.instantiateLayergroup() : this.createLayergroup(),
this.incrementMapViewCount(),
this.augmentLayergroupData(),
this.getAffectedTables(),
this.setCacheChannel(),
this.setLastModified(),
this.setLastUpdatedTimeToLayergroup(),
this.setCacheControl(),
this.setLayerStats(),
this.setLayergroupIdHeader(useTemplateHash),
this.setDataviewsAndWidgetsUrlsToLayergroupMetadata(),
this.setAnalysesMetadataToLayergroup(includeQuery),
this.setTurboCartoMetadataToLayergroup(),
this.setAggregationMetadataToLayergroup(),
this.setTilejsonMetadataToLayergroup(),
this.setSurrogateKeyHeader(),
this.sendResponse(),
this.augmentError({ label, addContext })
initProfiler(isTemplateInstantiation),
checkJsonContentType(),
this.getCreateMapMiddlewares(useTemplate),
incrementMapViewCount(this.metadataBackend),
augmentLayergroupData(),
getAffectedTables(this.pgConnection, this.layergroupAffectedTables),
setCacheChannel(),
setLastModified(),
setLastUpdatedTimeToLayergroup(),
setCacheControl(),
setLayerStats(this.pgConnection, this.statsBackend),
setLayergroupIdHeader(this.templateMaps ,useTemplateHash),
setDataviewsAndWidgetsUrlsToLayergroupMetadata(this.layergroupMetadata),
setAnalysesMetadataToLayergroup(this.layergroupMetadata, includeQuery),
setTurboCartoMetadataToLayergroup(this.layergroupMetadata),
setAggregationMetadataToLayergroup(this.layergroupMetadata),
setTilejsonMetadataToLayergroup(this.layergroupMetadata),
setSurrogateKeyHeader(this.surrogateKeysCache),
sendResponse(),
augmentError({ label, addContext })
];
};
MapController.prototype.initProfiler = function (isTemplateInstantiation) {
MapController.prototype.getCreateMapMiddlewares = function (useTemplate) {
if (useTemplate) {
return [
checkInstantiteLayergroup(),
getTemplate(
this.templateMaps,
this.pgConnection,
this.metadataBackend,
this.userLimitsApi,
this.mapConfigAdapter
),
instantiateLayergroup(this.mapBackend, this.userLimitsApi)
];
}
return [
checkCreateLayergroup(),
prepareAdapterMapConfig(this.mapConfigAdapter),
createLayergroup (this.mapBackend, this.userLimitsApi)
];
};
function initProfiler (isTemplateInstantiation) {
const operation = isTemplateInstantiation ? 'instance_template' : 'createmap';
return function initProfilerMiddleware (req, res, next) {
@@ -105,9 +122,9 @@ MapController.prototype.initProfiler = function (isTemplateInstantiation) {
req.profiler.done(`${operation}.initProfilerMiddleware`);
next();
};
};
}
MapController.prototype.checkJsonContentType = function () {
function checkJsonContentType () {
return function checkJsonContentTypeMiddleware(req, res, next) {
if (req.method === 'POST' && !req.is('application/json')) {
return next(new Error('POST data must be of type application/json'));
@@ -117,9 +134,9 @@ MapController.prototype.checkJsonContentType = function () {
next();
};
};
}
MapController.prototype.checkInstantiteLayergroup = function () {
function checkInstantiteLayergroup () {
return function checkInstantiteLayergroupMiddleware(req, res, next) {
if (req.method === 'GET') {
const { callback, config } = req.query;
@@ -141,9 +158,9 @@ MapController.prototype.checkInstantiteLayergroup = function () {
return next();
};
};
}
MapController.prototype.checkCreateLayergroup = function () {
function checkCreateLayergroup () {
return function checkCreateLayergroupMiddleware (req, res, next) {
if (req.method === 'GET') {
const { config } = res.locals;
@@ -162,19 +179,19 @@ MapController.prototype.checkCreateLayergroup = function () {
req.profiler.done('checkCreateLayergroup');
return next();
};
};
}
MapController.prototype.getTemplate = function () {
function getTemplate (templateMaps, pgConnection, metadataBackend, userLimitsApi, mapConfigAdapter) {
return function getTemplateMiddleware (req, res, next) {
const templateParams = req.body;
const { user } = res.locals;
const mapconfigProvider = new NamedMapMapConfigProvider(
this.templateMaps,
this.pgConnection,
this.metadataBackend,
this.userLimitsApi,
this.mapConfigAdapter,
templateMaps,
pgConnection,
metadataBackend,
userLimitsApi,
mapConfigAdapter,
user,
req.params.template_id,
templateParams,
@@ -194,10 +211,10 @@ MapController.prototype.getTemplate = function () {
next();
});
}.bind(this);
};
};
}
MapController.prototype.prepareAdapterMapConfig = function () {
function prepareAdapterMapConfig (mapConfigAdapter) {
return function prepareAdapterMapConfigMiddleware(req, res, next) {
const requestMapConfig = req.body;
const { user, dbhost, dbport, dbname, dbuser, dbpassword, api_key } = res.locals;
@@ -219,7 +236,7 @@ MapController.prototype.prepareAdapterMapConfig = function () {
}
};
this.mapConfigAdapter.getMapConfig(user, requestMapConfig, res.locals, context, (err, requestMapConfig) => {
mapConfigAdapter.getMapConfig(user, requestMapConfig, res.locals, context, (err, requestMapConfig) => {
req.profiler.done('anonymous.getMapConfig');
if (err) {
return next(err);
@@ -230,22 +247,22 @@ MapController.prototype.prepareAdapterMapConfig = function () {
next();
});
}.bind(this);
};
};
}
MapController.prototype.createLayergroup = function () {
function createLayergroup (mapBackend, userLimitsApi) {
return function createLayergroupMiddleware (req, res, next) {
const requestMapConfig = req.body;
const { context, user } = res.locals;
const datasource = context.datasource || Datasource.EmptyDatasource();
const mapconfig = new MapConfig(requestMapConfig, datasource);
const mapconfigProvider =
new CreateLayergroupMapConfigProvider(mapconfig, user, this.userLimitsApi, res.locals);
new CreateLayergroupMapConfigProvider(mapconfig, user, userLimitsApi, res.locals);
res.locals.mapconfig = mapconfig;
res.locals.analysesResults = context.analysesResults;
this.mapBackend.createLayergroup(mapconfig, res.locals, mapconfigProvider, (err, layergroup) => {
mapBackend.createLayergroup(mapconfig, res.locals, mapconfigProvider, (err, layergroup) => {
req.profiler.done('createLayergroup');
if (err) {
return next(err);
@@ -255,16 +272,16 @@ MapController.prototype.createLayergroup = function () {
next();
});
}.bind(this);
};
};
}
MapController.prototype.instantiateLayergroup = function () {
function instantiateLayergroup (mapBackend, userLimitsApi) {
return function instantiateLayergroupMiddleware (req, res, next) {
const { user, mapconfig, rendererParams } = res.locals;
const mapconfigProvider =
new CreateLayergroupMapConfigProvider(mapconfig, user, this.userLimitsApi, rendererParams);
new CreateLayergroupMapConfigProvider(mapconfig, user, userLimitsApi, rendererParams);
this.mapBackend.createLayergroup(mapconfig, rendererParams, mapconfigProvider, (err, layergroup) => {
mapBackend.createLayergroup(mapconfig, rendererParams, mapconfigProvider, (err, layergroup) => {
req.profiler.done('createLayergroup');
if (err) {
return next(err);
@@ -281,15 +298,15 @@ MapController.prototype.instantiateLayergroup = function () {
next();
});
}.bind(this);
};
};
}
MapController.prototype.incrementMapViewCount = function () {
function incrementMapViewCount (metadataBackend) {
return function incrementMapViewCountMiddleware(req, res, next) {
const { mapconfig, user } = res.locals;
// Error won't blow up, just be logged.
this.metadataBackend.incMapviewCount(user, mapconfig.obj().stat_tag, (err) => {
metadataBackend.incMapviewCount(user, mapconfig.obj().stat_tag, (err) => {
req.profiler.done('incMapviewCount');
if (err) {
@@ -298,10 +315,10 @@ MapController.prototype.incrementMapViewCount = function () {
next();
});
}.bind(this);
};
};
}
MapController.prototype.augmentLayergroupData = function () {
function augmentLayergroupData () {
return function augmentLayergroupDataMiddleware (req, res, next) {
const { layergroup } = res.locals;
@@ -312,91 +329,13 @@ MapController.prototype.augmentLayergroupData = function () {
next();
};
};
function getTemplateUrl(url) {
return url.https || url.http;
}
function getTilejson(tiles, grids) {
const tilejson = {
tilejson: '2.2.0',
tiles: tiles.https || tiles.http
};
if (grids) {
tilejson.grids = grids.https || grids.http;
}
return tilejson;
}
MapController.prototype.setTilejsonMetadataToLayergroup = function () {
return function augmentLayergroupTilejsonMiddleware (req, res, next) {
const { layergroup, user, mapconfig } = res.locals;
const isVectorOnlyMapConfig = mapconfig.isVectorOnlyMapConfig();
let hasMapnikLayers = false;
layergroup.metadata.layers.forEach((layerMetadata, index) => {
const layerId = mapconfig.getLayerId(index);
const rasterResource = `${layergroup.layergroupid}/${layerId}/{z}/{x}/{y}.png`;
if (mapconfig.layerType(index) === 'mapnik') {
hasMapnikLayers = true;
const vectorResource = `${layergroup.layergroupid}/${layerId}/{z}/{x}/{y}.mvt`;
const layerTilejson = {
vector: getTilejson(this.resourceLocator.getTileUrls(user, vectorResource))
};
if (!isVectorOnlyMapConfig) {
let grids = null;
const layer = mapconfig.getLayer(index);
if (layer.options.interactivity) {
const gridResource = `${layergroup.layergroupid}/${layerId}/{z}/{x}/{y}.grid.json`;
grids = this.resourceLocator.getTileUrls(user, gridResource);
}
layerTilejson.raster = getTilejson(
this.resourceLocator.getTileUrls(user, rasterResource),
grids
);
}
layerMetadata.tilejson = layerTilejson;
} else {
layerMetadata.tilejson = {
raster: getTilejson(this.resourceLocator.getTileUrls(user, rasterResource))
};
}
});
const tilejson = {};
const url = {};
if (hasMapnikLayers) {
const vectorResource = `${layergroup.layergroupid}/{z}/{x}/{y}.mvt`;
tilejson.vector = getTilejson(
this.resourceLocator.getTileUrls(user, vectorResource)
);
url.vector = getTemplateUrl(this.resourceLocator.getTemplateUrls(user, vectorResource));
if (!isVectorOnlyMapConfig) {
const rasterResource = `${layergroup.layergroupid}/{z}/{x}/{y}.png`;
tilejson.raster = getTilejson(
this.resourceLocator.getTileUrls(user, rasterResource)
);
url.raster = getTemplateUrl(this.resourceLocator.getTemplateUrls(user, rasterResource));
}
}
layergroup.metadata.tilejson = tilejson;
layergroup.metadata.url = url;
next();
}.bind(this);
};
MapController.prototype.getAffectedTables = function () {
function getAffectedTables (pgConnection, layergroupAffectedTables) {
return function getAffectedTablesMiddleware (req, res, next) {
const { dbname, layergroup, user, mapconfig } = res.locals;
this.pgConnection.getConnection(user, (err, connection) => {
pgConnection.getConnection(user, (err, connection) => {
if (err) {
return next(err);
}
@@ -418,17 +357,17 @@ MapController.prototype.getAffectedTables = function () {
}
// feed affected tables cache so it can be reused from, for instance, layergroup controller
this.layergroupAffectedTables.set(dbname, layergroup.layergroupId, affectedTables);
layergroupAffectedTables.set(dbname, layergroup.layergroupId, affectedTables);
res.locals.affectedTables = affectedTables;
next();
});
});
}.bind(this);
};
};
}
MapController.prototype.setCacheChannel = function () {
function setCacheChannel () {
return function setCacheChannelMiddleware (req, res, next) {
const { affectedTables } = res.locals;
@@ -438,9 +377,9 @@ MapController.prototype.setCacheChannel = function () {
next();
};
};
}
MapController.prototype.setLastModified = function () {
function setLastModified () {
return function setLastModifiedMiddleware (req, res, next) {
if (req.method === 'GET') {
res.set('Last-Modified', (new Date()).toUTCString());
@@ -448,9 +387,9 @@ MapController.prototype.setLastModified = function () {
next();
};
};
}
MapController.prototype.setLastUpdatedTimeToLayergroup = function () {
function setLastUpdatedTimeToLayergroup () {
return function setLastUpdatedTimeToLayergroupMiddleware (req, res, next) {
const { affectedTables, layergroup, analysesResults } = res.locals;
@@ -464,7 +403,7 @@ MapController.prototype.setLastUpdatedTimeToLayergroup = function () {
next();
};
};
}
function getLastUpdatedTime(analysesResults, lastUpdateTime) {
if (!Array.isArray(analysesResults)) {
@@ -479,7 +418,7 @@ function getLastUpdatedTime(analysesResults, lastUpdateTime) {
}, lastUpdateTime);
}
MapController.prototype.setCacheControl = function () {
function setCacheControl () {
return function setCacheControlMiddleware (req, res, next) {
if (req.method === 'GET') {
var ttl = global.environment.varnish.layergroupTtl || 86400;
@@ -488,18 +427,18 @@ MapController.prototype.setCacheControl = function () {
next();
};
};
}
MapController.prototype.setLayerStats = function () {
function setLayerStats (pgConnection, statsBackend) {
return function setLayerStatsMiddleware(req, res, next) {
const { user, mapconfig, layergroup } = res.locals;
this.pgConnection.getConnection(user, (err, connection) => {
pgConnection.getConnection(user, (err, connection) => {
if (err) {
return next(err);
}
this.statsBackend.getStats(mapconfig, connection, function(err, layersStats) {
statsBackend.getStats(mapconfig, connection, function(err, layersStats) {
if (err) {
return next(err);
}
@@ -513,171 +452,91 @@ MapController.prototype.setLayerStats = function () {
next();
});
});
}.bind(this);
};
};
}
MapController.prototype.setLayergroupIdHeader = function (useTemplateHash) {
function setLayergroupIdHeader (templateMaps, useTemplateHash) {
return function setLayergroupIdHeaderMiddleware (req, res, next) {
const { layergroup, user, template } = res.locals;
if (useTemplateHash) {
var templateHash = this.templateMaps.fingerPrint(template).substring(0, 8);
var templateHash = templateMaps.fingerPrint(template).substring(0, 8);
layergroup.layergroupid = `${user}@${templateHash}@${layergroup.layergroupid}`;
}
res.set('X-Layergroup-Id', layergroup.layergroupid);
next();
}.bind(this);
};
};
}
MapController.prototype.setDataviewsAndWidgetsUrlsToLayergroupMetadata = function () {
function setDataviewsAndWidgetsUrlsToLayergroupMetadata (layergroupMetadata) {
return function setDataviewsAndWidgetsUrlsToLayergroupMetadataMiddleware (req, res, next) {
const { layergroup, user, mapconfig } = res.locals;
this.addDataviewsAndWidgetsUrls(user, layergroup, mapconfig.obj());
layergroupMetadata.addDataviewsAndWidgetsUrls(user, layergroup, mapconfig.obj());
next();
}.bind(this);
};
};
}
// TODO this should take into account several URL patterns
MapController.prototype.addDataviewsAndWidgetsUrls = function(username, layergroup, mapConfig) {
this.addDataviewsUrls(username, layergroup, mapConfig);
this.addWidgetsUrl(username, layergroup, mapConfig);
};
MapController.prototype.addDataviewsUrls = function(username, layergroup, mapConfig) {
layergroup.metadata.dataviews = layergroup.metadata.dataviews || {};
var dataviews = mapConfig.dataviews || {};
Object.keys(dataviews).forEach(function(dataviewName) {
var resource = layergroup.layergroupid + '/dataview/' + dataviewName;
layergroup.metadata.dataviews[dataviewName] = {
url: this.resourceLocator.getUrls(username, resource)
};
}.bind(this));
};
MapController.prototype.addWidgetsUrl = function(username, layergroup, mapConfig) {
if (layergroup.metadata && Array.isArray(layergroup.metadata.layers) && Array.isArray(mapConfig.layers)) {
layergroup.metadata.layers = layergroup.metadata.layers.map(function(layer, layerIndex) {
var mapConfigLayer = mapConfig.layers[layerIndex];
if (mapConfigLayer.options && mapConfigLayer.options.widgets) {
layer.widgets = layer.widgets || {};
Object.keys(mapConfigLayer.options.widgets).forEach(function(widgetName) {
var resource = layergroup.layergroupid + '/' + layerIndex + '/widget/' + widgetName;
layer.widgets[widgetName] = {
type: mapConfigLayer.options.widgets[widgetName].type,
url: this.resourceLocator.getUrls(username, resource)
};
}.bind(this));
}
return layer;
}.bind(this));
}
};
MapController.prototype.setAnalysesMetadataToLayergroup = function (includeQuery) {
function setAnalysesMetadataToLayergroup (layergroupMetadata, includeQuery) {
return function setAnalysesMetadataToLayergroupMiddleware (req, res, next) {
const { layergroup, user, analysesResults = [] } = res.locals;
this.addAnalysesMetadata(user, layergroup, analysesResults, includeQuery);
layergroupMetadata.addAnalysesMetadata(user, layergroup, analysesResults, includeQuery);
next();
}.bind(this);
};
};
}
MapController.prototype.addAnalysesMetadata = function(username, layergroup, analysesResults, includeQuery) {
includeQuery = includeQuery || false;
analysesResults = analysesResults || [];
layergroup.metadata.analyses = [];
analysesResults.forEach(function(analysis) {
var nodes = analysis.getNodes();
layergroup.metadata.analyses.push({
nodes: nodes.reduce(function(nodesIdMap, node) {
if (node.params.id) {
var nodeResource = layergroup.layergroupid + '/analysis/node/' + node.id();
var nodeRepr = {
status: node.getStatus(),
url: this.resourceLocator.getUrls(username, nodeResource)
};
if (includeQuery) {
nodeRepr.query = node.getQuery();
}
if (node.getStatus() === 'failed') {
nodeRepr.error_message = node.getErrorMessage();
}
nodesIdMap[node.params.id] = nodeRepr;
}
return nodesIdMap;
}.bind(this), {})
});
}.bind(this));
};
MapController.prototype.setTurboCartoMetadataToLayergroup = function () {
function setTurboCartoMetadataToLayergroup (layergroupMetadata) {
return function setTurboCartoMetadataToLayergroupMiddleware (req, res, next) {
const { layergroup, mapconfig, context } = res.locals;
addTurboCartoContextMetadata(layergroup, mapconfig.obj(), context);
layergroupMetadata.addTurboCartoContextMetadata(layergroup, mapconfig.obj(), context);
next();
};
};
function addTurboCartoContextMetadata(layergroup, mapConfig, context) {
if (layergroup.metadata && Array.isArray(layergroup.metadata.layers) && Array.isArray(mapConfig.layers)) {
layergroup.metadata.layers = layergroup.metadata.layers.map(function(layer, layerIndex) {
if (context.turboCarto && Array.isArray(context.turboCarto.layers)) {
layer.meta.cartocss_meta = context.turboCarto.layers[layerIndex];
}
return layer;
});
}
}
// TODO: see how evolve this function, it's a good candidate to be refactored
MapController.prototype.setAggregationMetadataToLayergroup = function () {
function setAggregationMetadataToLayergroup (layergroupMetadata) {
return function setAggregationMetadataToLayergroupMiddleware (req, res, next) {
const { layergroup, mapconfig, context } = res.locals;
addAggregationContextMetadata(layergroup, mapconfig.obj(), context);
layergroupMetadata.addAggregationContextMetadata(layergroup, mapconfig.obj(), context);
next();
};
};
function addAggregationContextMetadata(layergroup, mapConfig, context) {
if (layergroup.metadata && Array.isArray(layergroup.metadata.layers) && Array.isArray(mapConfig.layers)) {
layergroup.metadata.layers = layergroup.metadata.layers.map(function(layer, layerIndex) {
if (context.aggregation && Array.isArray(context.aggregation.layers)) {
layer.meta.aggregation = context.aggregation.layers[layerIndex];
}
return layer;
});
}
}
MapController.prototype.setSurrogateKeyHeader = function () {
function setTilejsonMetadataToLayergroup (layergroupMetadata) {
return function augmentLayergroupTilejsonMiddleware (req, res, next) {
const { layergroup, user, mapconfig } = res.locals;
layergroupMetadata.addTileJsonMetadata(layergroup, user, mapconfig);
next();
};
}
function setSurrogateKeyHeader (surrogateKeysCache) {
return function setSurrogateKeyHeaderMiddleware(req, res, next) {
const { affectedTables, user, templateName } = res.locals;
if (req.method === 'GET' && affectedTables.tables && affectedTables.tables.length > 0) {
this.surrogateKeysCache.tag(res, affectedTables);
surrogateKeysCache.tag(res, affectedTables);
}
if (templateName) {
this.surrogateKeysCache.tag(res, new NamedMapsCacheEntry(user, templateName));
surrogateKeysCache.tag(res, new NamedMapsCacheEntry(user, templateName));
}
next();
}.bind(this);
};
};
}
MapController.prototype.sendResponse = function () {
function sendResponse () {
return function sendResponseMiddleware (req, res) {
req.profiler.done('res');
const { layergroup } = res.locals;
@@ -690,9 +549,9 @@ MapController.prototype.sendResponse = function () {
res.json(layergroup);
}
};
};
}
MapController.prototype.augmentError = function (options) {
function augmentError (options) {
const { addContext = false, label = 'MAPS CONTROLLER' } = options;
return function augmentErrorMiddleware (err, req, res, next) {
@@ -707,7 +566,7 @@ MapController.prototype.augmentError = function (options) {
next(err);
};
};
}
function populateError(err, mapConfig) {
var error = new Error(err.message);

View File

@@ -41,59 +41,73 @@ function NamedMapsController(prepareContext, namedMapProviderCache, tileBackend,
module.exports = NamedMapsController;
NamedMapsController.prototype.register = function(app) {
const { base_url_mapconfig, base_url_templated } = app;
app.get(
app.base_url_templated + '/:template_id/:layer/:z/:x/:y.(:format)',
`${base_url_templated}/:template_id/:layer/:z/:x/:y.(:format)`,
cors(),
userMiddleware,
userMiddleware(),
this.prepareContext,
this.getNamedMapProvider('NAMED_MAP_TILE'),
this.getAffectedTables(),
this.getTile('NAMED_MAP_TILE'),
this.setSurrogateKey(),
this.setCacheChannelHeader(),
this.setLastModifiedHeader(),
this.setCacheControlHeader(),
this.setContentTypeHeader(),
this.respond(),
getNamedMapProvider({
namedMapProviderCache: this.namedMapProviderCache,
label: 'NAMED_MAP_TILE'
}),
getAffectedTables(),
getTile({
tileBackend: this.tileBackend,
label: 'NAMED_MAP_TILE'
}),
setSurrogateKeyHeader({ surrogateKeysCache: this.surrogateKeysCache }),
setCacheChannelHeader(),
setLastModifiedHeader(),
setCacheControlHeader(),
setContentTypeHeader(),
sendResponse(),
vectorError()
);
app.get(
app.base_url_mapconfig + '/static/named/:template_id/:width/:height.:format',
`${base_url_mapconfig}/static/named/:template_id/:width/:height.:format`,
cors(),
userMiddleware,
userMiddleware(),
allowQueryParams(['layer', 'zoom', 'lon', 'lat', 'bbox']),
this.prepareContext,
this.getNamedMapProvider('STATIC_VIZ_MAP'),
this.getAffectedTables(),
this.getTemplate('STATIC_VIZ_MAP'),
this.prepareLayerFilterFromPreviewLayers('STATIC_VIZ_MAP'),
this.getStaticImageOptions(),
this.getImage('STATIC_VIZ_MAP'),
this.incrementMapViews(),
this.setSurrogateKey(),
this.setCacheChannelHeader(),
this.setLastModifiedHeader(),
this.setCacheControlHeader(),
this.setContentTypeHeader(),
this.respond()
getNamedMapProvider({
namedMapProviderCache: this.namedMapProviderCache,
label: 'STATIC_VIZ_MAP', forcedFormat: 'png'
}),
getAffectedTables(),
getTemplate({ label: 'STATIC_VIZ_MAP' }),
prepareLayerFilterFromPreviewLayers({
namedMapProviderCache: this.namedMapProviderCache,
label: 'STATIC_VIZ_MAP'
}),
getStaticImageOptions({ tablesExtentApi: this.tablesExtentApi }),
getImage({ previewBackend: this.previewBackend, label: 'STATIC_VIZ_MAP' }),
incrementMapViews({ metadataBackend: this.metadataBackend }),
setSurrogateKeyHeader({ surrogateKeysCache: this.surrogateKeysCache }),
setCacheChannelHeader(),
setLastModifiedHeader(),
setCacheControlHeader(),
setContentTypeHeader(),
sendResponse()
);
};
NamedMapsController.prototype.getNamedMapProvider = function (label) {
function getNamedMapProvider ({ namedMapProviderCache, label, forcedFormat = null }) {
return function getNamedMapProviderMiddleware (req, res, next) {
const { user } = res.locals;
const { config, auth_token } = req.query;
const { template_id } = req.params;
// We force always the tile to be generated using PNG because
// is the only format we support by now
res.locals.format = 'png';
res.locals.layer = res.locals.layer || 'all';
if (forcedFormat) {
res.locals.format = forcedFormat;
res.locals.layer = res.locals.layer || 'all';
}
const params = getRequestParams(res.locals);
this.namedMapProviderCache.get(user, template_id, config, auth_token, params, (err, namedMapProvider) => {
namedMapProviderCache.get(user, template_id, config, auth_token, params, (err, namedMapProvider) => {
if (err) {
err.label = label;
return next(err);
@@ -103,10 +117,10 @@ NamedMapsController.prototype.getNamedMapProvider = function (label) {
next();
});
}.bind(this);
};
};
}
NamedMapsController.prototype.getAffectedTables = function () {
function getAffectedTables () {
return function getAffectedTables (req, res, next) {
const { namedMapProvider } = res.locals;
@@ -121,10 +135,10 @@ NamedMapsController.prototype.getAffectedTables = function () {
next();
});
}.bind(this);
};
};
}
NamedMapsController.prototype.getTemplate = function (label) {
function getTemplate ({ label }) {
return function getTemplateMiddleware (req, res, next) {
const { namedMapProvider } = res.locals;
@@ -139,9 +153,9 @@ NamedMapsController.prototype.getTemplate = function (label) {
next();
});
};
};
}
NamedMapsController.prototype.prepareLayerFilterFromPreviewLayers = function (label) {
function prepareLayerFilterFromPreviewLayers ({ namedMapProviderCache, label }) {
return function prepareLayerFilterFromPreviewLayersMiddleware (req, res, next) {
const { user, template } = res.locals;
const { template_id } = req.params;
@@ -170,7 +184,7 @@ NamedMapsController.prototype.prepareLayerFilterFromPreviewLayers = function (la
params.layer = layerVisibilityFilter.join(',');
// recreates the provider
this.namedMapProviderCache.get(user, template_id, config, auth_token, params, (err, provider) => {
namedMapProviderCache.get(user, template_id, config, auth_token, params, (err, provider) => {
if (err) {
err.label = label;
return next(err);
@@ -180,14 +194,14 @@ NamedMapsController.prototype.prepareLayerFilterFromPreviewLayers = function (la
next();
});
}.bind(this);
};
};
}
NamedMapsController.prototype.getTile = function (label) {
function getTile ({ tileBackend, label }) {
return function getTileMiddleware (req, res, next) {
const { namedMapProvider } = res.locals;
this.tileBackend.getTile(namedMapProvider, req.params, (err, tile, headers, stats) => {
tileBackend.getTile(namedMapProvider, req.params, (err, tile, headers, stats) => {
req.profiler.add(stats);
if (err) {
@@ -201,10 +215,10 @@ NamedMapsController.prototype.getTile = function (label) {
next();
});
}.bind(this);
};
};
}
NamedMapsController.prototype.getStaticImageOptions = function () {
function getStaticImageOptions ({ tablesExtentApi }) {
return function getStaticImageOptionsMiddleware(req, res, next) {
const { user, namedMapProvider, template } = res.locals;
@@ -228,7 +242,7 @@ NamedMapsController.prototype.getStaticImageOptions = function () {
return next();
}
this.tablesExtentApi.getBounds(user, affectedTables, (err, bounds) => {
tablesExtentApi.getBounds(user, affectedTables, (err, bounds) => {
if (err) {
return next();
}
@@ -238,8 +252,8 @@ NamedMapsController.prototype.getStaticImageOptions = function () {
return next();
});
});
}.bind(this);
};
};
}
function getImageOptions (params, template) {
const { zoom, lon, lat, bbox } = params;
@@ -306,7 +320,7 @@ function getImageOptionsFromBoundingBox (bbox = '') {
}
}
NamedMapsController.prototype.getImage = function (label) {
function getImage({ previewBackend, label }) {
return function getImageMiddleware (req, res, next) {
const { imageOpts, namedMapProvider } = res.locals;
const { zoom, center, bounds } = imageOpts;
@@ -319,7 +333,7 @@ NamedMapsController.prototype.getImage = function (label) {
const format = req.params.format === 'jpg' ? 'jpeg' : 'png';
if (zoom !== undefined && center) {
return this.previewBackend.getImage(namedMapProvider, format, width, height, zoom, center,
return previewBackend.getImage(namedMapProvider, format, width, height, zoom, center,
(err, image, headers, stats) => {
if (err) {
err.label = label;
@@ -334,7 +348,7 @@ NamedMapsController.prototype.getImage = function (label) {
});
}
this.previewBackend.getImage(namedMapProvider, format, width, height, bounds, (err, image, headers, stats) => {
previewBackend.getImage(namedMapProvider, format, width, height, bounds, (err, image, headers, stats) => {
if (err) {
err.label = label;
return next(err);
@@ -346,14 +360,14 @@ NamedMapsController.prototype.getImage = function (label) {
next();
});
}.bind(this);
};
};
}
function incrementMapViewsError (ctx) {
return `ERROR: failed to increment mapview count for user '${ctx.user}': ${ctx.err}`;
}
NamedMapsController.prototype.incrementMapViews = function () {
function incrementMapViews ({ metadataBackend }) {
return function incrementMapViewsMiddleware(req, res, next) {
const { user, namedMapProvider } = res.locals;
@@ -365,7 +379,7 @@ NamedMapsController.prototype.incrementMapViews = function () {
const statTag = mapConfig.obj().stat_tag;
this.metadataBackend.incMapviewCount(user, statTag, (err) => {
metadataBackend.incMapviewCount(user, statTag, (err) => {
if (err) {
global.logger.log(incrementMapViewsError({ user, err }));
}
@@ -373,8 +387,8 @@ NamedMapsController.prototype.incrementMapViews = function () {
next();
});
});
}.bind(this);
};
};
}
function templateZoomCenter(view) {
if (view.zoom !== undefined && view.center) {
@@ -406,7 +420,22 @@ function templateBounds(view) {
return false;
}
NamedMapsController.prototype.setCacheChannelHeader = function () {
function setSurrogateKeyHeader ({ surrogateKeysCache }) {
return function setSurrogateKeyHeaderMiddleware(req, res, next) {
const { user, namedMapProvider, affectedTablesAndLastUpdate } = res.locals;
surrogateKeysCache.tag(res, new NamedMapsCacheEntry(user, namedMapProvider.getTemplateName()));
if (!affectedTablesAndLastUpdate || !!affectedTablesAndLastUpdate.tables) {
if (affectedTablesAndLastUpdate.tables.length > 0) {
surrogateKeysCache.tag(res, affectedTablesAndLastUpdate);
}
}
next();
};
}
function setCacheChannelHeader () {
return function setCacheChannelHeaderMiddleware (req, res, next) {
const { affectedTablesAndLastUpdate } = res.locals;
@@ -416,24 +445,9 @@ NamedMapsController.prototype.setCacheChannelHeader = function () {
next();
};
};
}
NamedMapsController.prototype.setSurrogateKey = function () {
return function setSurrogateKeyMiddleware(req, res, next) {
const { user, namedMapProvider, affectedTablesAndLastUpdate } = res.locals;
this.surrogateKeysCache.tag(res, new NamedMapsCacheEntry(user, namedMapProvider.getTemplateName()));
if (!affectedTablesAndLastUpdate || !!affectedTablesAndLastUpdate.tables) {
if (affectedTablesAndLastUpdate.tables.length > 0) {
this.surrogateKeysCache.tag(res, affectedTablesAndLastUpdate);
}
}
next();
}.bind(this);
};
NamedMapsController.prototype.setLastModifiedHeader = function () {
function setLastModifiedHeader () {
return function setLastModifiedHeaderMiddleware(req, res, next) {
const { affectedTablesAndLastUpdate } = res.locals;
@@ -450,9 +464,9 @@ NamedMapsController.prototype.setLastModifiedHeader = function () {
next();
};
};
}
NamedMapsController.prototype.setCacheControlHeader = function () {
function setCacheControlHeader () {
return function setCacheControlHeaderMiddleware(req, res, next) {
const { affectedTablesAndLastUpdate } = res.locals;
@@ -465,9 +479,9 @@ NamedMapsController.prototype.setCacheControlHeader = function () {
next();
};
};
}
NamedMapsController.prototype.setContentTypeHeader = function () {
function setContentTypeHeader () {
return function setContentTypeHeaderMiddleware(req, res, next) {
const { headers = {} } = res.locals;
@@ -475,10 +489,10 @@ NamedMapsController.prototype.setContentTypeHeader = function () {
next();
};
};
}
NamedMapsController.prototype.respond = function () {
return function respondMiddleware (req, res) {
function sendResponse () {
return function sendResponseMiddleware (req, res) {
const { body, stats = {}, format } = res.locals;
req.profiler.done('render-' + format);
@@ -487,4 +501,4 @@ NamedMapsController.prototype.respond = function () {
res.status(200);
res.send(body);
};
};
}

View File

@@ -1,6 +1,8 @@
const { templateName } = require('../backends/template_maps');
const cors = require('../middleware/cors');
const userMiddleware = require('../middleware/user');
const localsMiddleware = require('../middleware/context/locals');
const credentialsMiddleware = require('../middleware/context/credentials');
/**
* @param {AuthApi} authApi
@@ -21,43 +23,58 @@ NamedMapsAdminController.prototype.register = function (app) {
app.post(
`${base_url_templated}/`,
cors(),
userMiddleware,
this.checkContentType('POST', 'POST TEMPLATE'),
this.authorizedByAPIKey('create', 'POST TEMPLATE'),
this.create()
userMiddleware(),
localsMiddleware(),
credentialsMiddleware(),
checkContentType({ action: 'POST', label: 'POST TEMPLATE' }),
authorizedByAPIKey({ authApi: this.authApi, action: 'create', label: 'POST TEMPLATE' }),
createTemplate({ templateMaps: this.templateMaps }),
sendResponse()
);
app.put(
`${base_url_templated}/:template_id`,
cors(),
userMiddleware,
this.checkContentType('PUT', 'PUT TEMPLATE'),
this.authorizedByAPIKey('update', 'PUT TEMPLATE'),
this.update()
userMiddleware(),
localsMiddleware(),
credentialsMiddleware(),
checkContentType({ action: 'PUT', label: 'PUT TEMPLATE' }),
authorizedByAPIKey({ authApi: this.authApi, action: 'update', label: 'PUT TEMPLATE' }),
updateTemplate({ templateMaps: this.templateMaps }),
sendResponse()
);
app.get(
`${base_url_templated}/:template_id`,
cors(),
userMiddleware,
this.authorizedByAPIKey('get', 'GET TEMPLATE'),
this.retrieve()
userMiddleware(),
localsMiddleware(),
credentialsMiddleware(),
authorizedByAPIKey({ authApi: this.authApi, action: 'get', label: 'GET TEMPLATE' }),
retrieveTemplate({ templateMaps: this.templateMaps }),
sendResponse()
);
app.delete(
`${base_url_templated}/:template_id`,
cors(),
userMiddleware,
this.authorizedByAPIKey('delete', 'DELETE TEMPLATE'),
this.destroy()
userMiddleware(),
localsMiddleware(),
credentialsMiddleware(),
authorizedByAPIKey({ authApi: this.authApi, action: 'delete', label: 'DELETE TEMPLATE' }),
destroyTemplate({ templateMaps: this.templateMaps }),
sendResponse()
);
app.get(
`${base_url_templated}/`,
cors(),
userMiddleware,
this.authorizedByAPIKey('list', 'GET TEMPLATE LIST'),
this.list()
userMiddleware(),
localsMiddleware(),
credentialsMiddleware(),
authorizedByAPIKey({ authApi: this.authApi, action: 'list', label: 'GET TEMPLATE LIST' }),
listTemplates({ templateMaps: this.templateMaps }),
sendResponse()
);
app.options(
@@ -66,11 +83,23 @@ NamedMapsAdminController.prototype.register = function (app) {
);
};
NamedMapsAdminController.prototype.authorizedByAPIKey = function (action, label) {
function checkContentType ({ action, label }) {
return function checkContentTypeMiddleware (req, res, next) {
if (!req.is('application/json')) {
const error = new Error(`template ${action} data must be of type application/json`);
error.label = label;
return next(error);
}
next();
};
}
function authorizedByAPIKey ({ authApi, action, label }) {
return function authorizedByAPIKeyMiddleware (req, res, next) {
const { user } = res.locals;
this.authApi.authorizedByAPIKey(user, req, (err, authenticated) => {
authApi.authorizedByAPIKey(user, res, (err, authenticated) => {
if (err) {
return next(err);
}
@@ -84,65 +113,52 @@ NamedMapsAdminController.prototype.authorizedByAPIKey = function (action, label)
next();
});
}.bind(this);
};
NamedMapsAdminController.prototype.checkContentType = function (action, label) {
return function checkContentTypeMiddleware (req, res, next) {
if (!req.is('application/json')) {
const error = new Error(`template ${action} data must be of type application/json`);
error.label = label;
return next(error);
}
next();
};
};
}
NamedMapsAdminController.prototype.create = function () {
function createTemplate ({ templateMaps }) {
return function createTemplateMiddleware (req, res, next) {
const { user } = res.locals;
const template = req.body;
this.templateMaps.addTemplate(user, template, (err, templateId) => {
templateMaps.addTemplate(user, template, (err, templateId) => {
if (err) {
return next(err);
}
res.status(200);
res.body = { template_id: templateId };
const method = req.query.callback ? 'jsonp' : 'json';
res[method]({ template_id: templateId });
next();
});
}.bind(this);
};
};
}
NamedMapsAdminController.prototype.update = function () {
function updateTemplate ({ templateMaps }) {
return function updateTemplateMiddleware (req, res, next) {
const { user } = res.locals;
const template = req.body;
const templateId = templateName(req.params.template_id);
this.templateMaps.updTemplate(user, templateId, template, (err) => {
templateMaps.updTemplate(user, templateId, template, (err) => {
if (err) {
return next(err);
}
res.status(200);
res.body = { template_id: templateId };
const method = req.query.callback ? 'jsonp' : 'json';
res[method]({ template_id: templateId });
next();
});
}.bind(this);
};
};
}
NamedMapsAdminController.prototype.retrieve = function () {
function retrieveTemplate ({ templateMaps }) {
return function retrieveTemplateMiddleware (req, res, next) {
req.profiler.start('windshaft-cartodb.get_template');
const { user } = res.locals;
const templateId = templateName(req.params.template_id);
this.templateMaps.getTemplate(user, templateId, (err, template) => {
templateMaps.getTemplate(user, templateId, (err, template) => {
if (err) {
return next(err);
}
@@ -156,49 +172,56 @@ NamedMapsAdminController.prototype.retrieve = function () {
// so we remove it before returning to the user
delete template.auth_id;
res.status(200);
res.body = { template };
const method = req.query.callback ? 'jsonp' : 'json';
res[method]({ template });
next();
});
}.bind(this);
};
};
}
NamedMapsAdminController.prototype.destroy = function () {
function destroyTemplate ({ templateMaps }) {
return function destroyTemplateMiddleware (req, res, next) {
req.profiler.start('windshaft-cartodb.delete_template');
const { user } = res.locals;
const templateId = templateName(req.params.template_id);
this.templateMaps.delTemplate(user, templateId, (err/* , tpl_val */) => {
templateMaps.delTemplate(user, templateId, (err/* , tpl_val */) => {
if (err) {
return next(err);
}
res.status(204);
res.statusCode = 204;
res.body = '';
const method = req.query.callback ? 'jsonp' : 'json';
res[method]('');
next();
});
}.bind(this);
};
};
}
NamedMapsAdminController.prototype.list = function () {
function listTemplates ({ templateMaps }) {
return function listTemplatesMiddleware (req, res, next) {
req.profiler.start('windshaft-cartodb.get_template_list');
const { user } = res.locals;
this.templateMaps.listTemplates(user, (err, templateIds) => {
templateMaps.listTemplates(user, (err, templateIds) => {
if (err) {
return next(err);
}
res.status(200);
res.body = { template_ids: templateIds };
const method = req.query.callback ? 'jsonp' : 'json';
res[method]({ template_ids: templateIds });
next();
});
}.bind(this);
};
};
}
function sendResponse () {
return function sendResponseMiddleware (req, res) {
res.status(res.statusCode || 200);
const method = req.query.callback ? 'jsonp' : 'json';
res[method](res.body);
};
}

View File

@@ -1,8 +1,9 @@
module.exports = function allowQueryParams(params) {
module.exports = function allowQueryParams (params) {
if (!Array.isArray(params)) {
throw new Error('allowQueryParams must receive an Array of params');
}
return function allowQueryParamsMiddleware(req, res, next) {
return function allowQueryParamsMiddleware (req, res, next) {
res.locals.allowedQueryParams = params;
next();
};

View File

@@ -1,9 +1,8 @@
module.exports = function authorizeMiddleware (authApi) {
return function (req, res, next) {
req.profiler.done('req2params.setup');
module.exports = function authorize (authApi) {
return function authorizeMiddleware (req, res, next) {
authApi.authorize(req, res, (err, authorized) => {
req.profiler.done('authorize');
if (err) {
return next(err);
}

View File

@@ -0,0 +1,85 @@
const basicAuth = require('basic-auth');
module.exports = function credentials () {
return function credentialsMiddleware(req, res, next) {
const apikeyCredentials = getApikeyCredentialsFromRequest(req);
res.locals.api_key = apikeyCredentials.token;
res.locals.basicAuthUsername = apikeyCredentials.username;
res.set('vary', 'Authorization'); //Honor Authorization header when caching.
return next();
};
};
function getApikeyCredentialsFromRequest(req) {
let apikeyCredentials = {
token: null,
username: null,
};
for (let getter of apikeyGetters) {
apikeyCredentials = getter(req);
if (apikeyTokenFound(apikeyCredentials)) {
break;
}
}
return apikeyCredentials;
}
const apikeyGetters = [
getApikeyTokenFromHeaderAuthorization,
getApikeyTokenFromRequestQueryString,
getApikeyTokenFromRequestBody,
];
function getApikeyTokenFromHeaderAuthorization(req) {
const credentials = basicAuth(req);
if (credentials) {
return {
username: credentials.username,
token: credentials.pass
};
} else {
return {
username: null,
token: null,
};
}
}
function getApikeyTokenFromRequestQueryString(req) {
let token = null;
if (req.query && req.query.api_key) {
token = req.query.api_key;
} else if (req.query && req.query.map_key) {
token = req.query.map_key;
}
return {
username: null,
token: token,
};
}
function getApikeyTokenFromRequestBody(req) {
let token = null;
if (req.body && req.body.api_key) {
token = req.body.api_key;
} else if (req.body && req.body.map_key) {
token = req.body.map_key;
}
return {
username: null,
token: token,
};
}
function apikeyTokenFound(apikey) {
return !!apikey && !!apikey.token;
}

View File

@@ -1,31 +1,30 @@
const _ = require('underscore');
module.exports = function dbConnSetupMiddleware(pgConnection) {
return function dbConnSetup(req, res, next) {
const user = res.locals.user;
module.exports = function dbConnSetup (pgConnection) {
return function dbConnSetupMiddleware (req, res, next) {
const { user } = res.locals;
pgConnection.setDBConn(user, res.locals, (err) => {
req.profiler.done('dbConnSetup');
if (err) {
if (err.message && -1 !== err.message.indexOf('name not found')) {
err.http_status = 404;
}
req.profiler.done('req2params');
return next(err);
}
// Add default database connection parameters
// if none given
_.defaults(res.locals, {
dbuser: global.environment.postgres.user,
dbpassword: global.environment.postgres.password,
dbhost: global.environment.postgres.host,
dbport: global.environment.postgres.port
});
res.set('X-Served-By-DB-Host', res.locals.dbhost);
req.profiler.done('req2params');
next(null);
next();
});
};
};

View File

@@ -1,14 +1,16 @@
const locals = require('./locals');
const cleanUpQueryParams = require('./clean-up-query-params');
const layergroupToken = require('./layergroup-token');
const credentials = require('./credentials');
const authorize = require('./authorize');
const dbConnSetup = require('./db-conn-setup');
module.exports = function prepareContextMiddleware(authApi, pgConnection) {
return [
locals,
locals(),
cleanUpQueryParams(),
layergroupToken,
layergroupToken(),
credentials(),
authorize(authApi),
dbConnSetup(pgConnection)
];

View File

@@ -1,32 +1,33 @@
var LayergroupToken = require('../../models/layergroup-token');
module.exports = function layergroupTokenMiddleware(req, res, next) {
if (!res.locals.token) {
return next();
}
var user = res.locals.user;
var layergroupToken = LayergroupToken.parse(res.locals.token);
res.locals.token = layergroupToken.token;
res.locals.cache_buster = layergroupToken.cacheBuster;
if (layergroupToken.signer) {
res.locals.signer = layergroupToken.signer;
if (!res.locals.signer) {
res.locals.signer = user;
} else if (res.locals.signer !== user) {
var err = new Error(`Cannot use map signature of user "${res.locals.signer}" on db of user "${user}"`);
err.type = 'auth';
err.http_status = 403;
if (req.query && req.query.callback) {
err.http_status = 200;
}
req.profiler.done('req2params');
return next(err);
}
}
return next();
const LayergroupToken = require('../../models/layergroup-token');
const authErrorMessageTemplate = function (signer, user) {
return `Cannot use map signature of user "${signer}" on db of user "${user}"`;
};
module.exports = function layergroupToken () {
return function layergroupTokenMiddleware (req, res, next) {
if (!res.locals.token) {
return next();
}
const user = res.locals.user;
const layergroupToken = LayergroupToken.parse(res.locals.token);
res.locals.token = layergroupToken.token;
res.locals.cache_buster = layergroupToken.cacheBuster;
if (layergroupToken.signer) {
res.locals.signer = layergroupToken.signer;
if (res.locals.signer !== user) {
const err = new Error(authErrorMessageTemplate(res.locals.signer, user));
err.type = 'auth';
err.http_status = (req.query && req.query.callback) ? 200: 403;
return next(err);
}
}
return next();
};
};

View File

@@ -1,6 +1,7 @@
module.exports = function localsMiddleware(req, res, next) {
// save req.params in res.locals
res.locals = Object.assign(req.params || {}, res.locals);
module.exports = function locals () {
return function localsMiddleware (req, res, next) {
res.locals = Object.assign(req.params || {}, res.locals);
next();
next();
};
};

View File

@@ -1,11 +1,14 @@
module.exports = function cors(extraHeaders) {
return function(req, res, next) {
var baseHeaders = "X-Requested-With, X-Prototype-Version, X-CSRF-Token";
module.exports = function cors (extraHeaders) {
return function corsMiddleware (req, res, next) {
let baseHeaders = "X-Requested-With, X-Prototype-Version, X-CSRF-Token";
if(extraHeaders) {
baseHeaders += ", " + extraHeaders;
}
res.set("Access-Control-Allow-Origin", "*");
res.set("Access-Control-Allow-Headers", baseHeaders);
next();
};
};

View File

@@ -1,30 +1,33 @@
'use strict';
const LZMA = require('lzma').LZMA;
const lzmaWorker = new LZMA();
module.exports = function lzma () {
const lzmaWorker = new LZMA();
module.exports = function lzmaMiddleware(req, res, next) {
if (!req.query.hasOwnProperty('lzma')) {
return next();
}
// Decode (from base64)
var lzma = new Buffer(req.query.lzma, 'base64')
.toString('binary')
.split('')
.map(function(c) {
return c.charCodeAt(0) - 128;
});
// Decompress
lzmaWorker.decompress(lzma, function(result) {
try {
delete req.query.lzma;
Object.assign(req.query, JSON.parse(result));
next();
} catch (err) {
next(new Error('Error parsing lzma as JSON: ' + err));
return function lzmaMiddleware (req, res, next) {
if (!req.query.hasOwnProperty('lzma')) {
return next();
}
});
// Decode (from base64)
var lzma = new Buffer(req.query.lzma, 'base64')
.toString('binary')
.split('')
.map(function(c) {
return c.charCodeAt(0) - 128;
});
// Decompress
lzmaWorker.decompress(lzma, function(result) {
try {
delete req.query.lzma;
Object.assign(req.query, JSON.parse(result));
req.profiler.done('lzma');
next();
} catch (err) {
next(new Error('Error parsing lzma as JSON: ' + err));
}
});
};
};

View File

@@ -2,10 +2,10 @@ const Profiler = require('../stats/profiler_proxy');
const debug = require('debug')('windshaft:cartodb:stats');
const onHeaders = require('on-headers');
module.exports = function statsMiddleware(options) {
module.exports = function stats (options) {
const { enabled = true, statsClient } = options;
return function stats(req, res, next) {
return function statsMiddleware (req, res, next) {
req.profiler = new Profiler({
statsd_client: statsClient,
profile: enabled

View File

@@ -1,8 +1,11 @@
var CdbRequest = require('../models/cdb_request');
var cdbRequest = new CdbRequest();
const CdbRequest = require('../models/cdb_request');
module.exports = function userMiddleware(req, res, next) {
res.locals.user = cdbRequest.userByReq(req);
module.exports = function user () {
const cdbRequest = new CdbRequest();
next();
return function userMiddleware(req, res, next) {
res.locals.user = cdbRequest.userByReq(req);
next();
};
};

View File

@@ -1,5 +1,4 @@
const fs = require('fs');
const timeoutErrorVectorTile = fs.readFileSync(__dirname + '/../../../assets/render-timeout-fallback.mvt');
module.exports = function vectorError() {

View File

@@ -131,7 +131,12 @@ const dimensionDefs = ctx => {
// This is equivalent to `${256/ctx.res}*CDB_XYZ_Resolution(CDB_ZoomFromScale(!scale_denominator!))`
// This is defined by the ctx.res parameter, which is the number of grid cells per tile linear dimension
// (i.e. each tile is divided into ctx.res*ctx.res cells).
const gridResolution = ctx => `(${256*0.00028/ctx.res}*!scale_denominator!)::double precision`;
// We limit the the minimum resolution to avoid division by zero problems. The limit used is
// the pixel size of zoom level 30 (i.e. 1/2*(30+8) of the full earth web-mercator extent), which is about 0.15 mm.
const gridResolution = ctx => {
const minimumResolution = 2*Math.PI*6378137/Math.pow(2,38);
return `GREATEST(${256*0.00028/ctx.res}*!scale_denominator!, ${minimumResolution})::double precision`;
};
// Notes:
// * We need to filter spatially using !bbox! to make the queries efficient because

View File

@@ -2,19 +2,17 @@ const BaseDataview = require('./base');
const debug = require('debug')('windshaft:dataview:aggregation');
const filteredQueryTpl = ctx => `
filtered_source AS (
SELECT *
FROM (${ctx.query}) _cdb_filtered_source
${ctx.aggregationColumn && ctx.isFloatColumn ? `
WHERE
${ctx.aggregationColumn} != 'infinity'::float
AND
${ctx.aggregationColumn} != '-infinity'::float
AND
${ctx.aggregationColumn} != 'NaN'::float` :
''
}
)
SELECT *
FROM (${ctx.query}) _cdb_filtered_source
${ctx.aggregationColumn && ctx.isFloatColumn ? `
WHERE
${ctx.aggregationColumn} != 'infinity'::float
AND
${ctx.aggregationColumn} != '-infinity'::float
AND
${ctx.aggregationColumn} != 'NaN'::float` :
''
}
`;
const summaryQueryTpl = ctx => `
@@ -43,8 +41,8 @@ const rankedCategoriesQueryTpl = ctx => `
${ctx.column} AS category,
${ctx.aggregationFn} AS value,
row_number() OVER (ORDER BY ${ctx.aggregationFn} desc) as rank
FROM filtered_source
${ctx.aggregationColumn !== null ? `WHERE ${ctx.aggregationColumn} IS NOT NULL` : ''}
FROM (${filteredQueryTpl(ctx)}) filtered_source
WHERE ${ctx.aggregation === "count" ? `${ctx.column}` : `${ctx.aggregationColumn}`} IS NOT NULL
GROUP BY ${ctx.column}
ORDER BY 2 DESC
)
@@ -134,7 +132,6 @@ const aggregationFnQueryTpl = ctx => `${ctx.aggregation}(${ctx.aggregationColumn
const aggregationDataviewQueryTpl = ctx => `
WITH
${filteredQueryTpl(ctx)},
${summaryQueryTpl(ctx)},
${rankedCategoriesQueryTpl(ctx)},
${categoriesSummaryMinMaxQueryTpl(ctx)},
@@ -282,7 +279,7 @@ module.exports = class Aggregation extends BaseDataview {
max_val = 0,
categories_count = 0
} = result.rows[0] || {};
return {
aggregation: this.aggregation,
count: count,
@@ -293,10 +290,10 @@ module.exports = class Aggregation extends BaseDataview {
max: max_val,
categoriesCount: categories_count,
categories: result.rows.map(({ category, value, agg }) => {
return {
return {
category: agg ? 'Other' : category,
value,
agg
value,
agg
};
})
};

View File

@@ -108,8 +108,7 @@ MapConfigNamedLayersAdapter.prototype.getMapConfig = function (user, requestMapC
var dbAuth = {};
if (_.some(layers, isNamedTypeLayer)) {
// Lazy load dbAuth
this.pgConnection.setDBAuth(user, dbAuth, function(err) {
this.pgConnection.setDBAuth(user, dbAuth, 'master', function(err) {
if (err) {
return callback(err);
}

View File

@@ -232,19 +232,19 @@ function configHash(config) {
module.exports.configHash = configHash;
NamedMapMapConfigProvider.prototype.setDBParams = function(cdbuser, params, callback) {
var self = this;
step(
function setAuth() {
self.pgConnection.setDBAuth(cdbuser, params, this);
},
function setConn(err) {
assert.ifError(err);
self.pgConnection.setDBConn(cdbuser, params, this);
},
function finish(err) {
callback(err);
this.pgConnection.getDatabaseParams(cdbuser, (err, databaseParams) => {
if (err) {
return callback(err);
}
);
params.dbuser = databaseParams.dbuser;
params.dbpass = databaseParams.dbpass;
params.dbhost = databaseParams.dbhost;
params.dbport = databaseParams.dbport;
params.dbname = databaseParams.dbname;
callback();
});
};
NamedMapMapConfigProvider.prototype.getTemplateName = function() {

View File

@@ -377,7 +377,7 @@ function bootstrap(opts) {
statsClient: global.statsClient
}));
app.use(lzmaMiddleware);
app.use(lzmaMiddleware());
// temporary measure until we upgrade to newer version expressjs so we can check err.status
app.use(function(err, req, res, next) {

View File

@@ -15,6 +15,7 @@ var rendererConfig = _.defaults(global.environment.renderer || {}, {
bufferSize: 64,
snapToGrid: false,
clipByBox2d: false,
metrics: false,
limits: {}
},
http: {}

View File

@@ -0,0 +1,168 @@
module.exports = class LayergroupMetadata {
constructor (resourceLocator) {
this.resourceLocator = resourceLocator;
}
// TODO this should take into account several URL patterns
addDataviewsAndWidgetsUrls (username, layergroup, mapConfig) {
this._addDataviewsUrls(username, layergroup, mapConfig);
this._addWidgetsUrl(username, layergroup, mapConfig);
}
_addDataviewsUrls (username, layergroup, mapConfig) {
layergroup.metadata.dataviews = layergroup.metadata.dataviews || {};
var dataviews = mapConfig.dataviews || {};
Object.keys(dataviews).forEach((dataviewName) => {
var resource = layergroup.layergroupid + '/dataview/' + dataviewName;
layergroup.metadata.dataviews[dataviewName] = {
url: this.resourceLocator.getUrls(username, resource)
};
});
}
_addWidgetsUrl (username, layergroup, mapConfig) {
if (layergroup.metadata && Array.isArray(layergroup.metadata.layers) && Array.isArray(mapConfig.layers)) {
layergroup.metadata.layers = layergroup.metadata.layers.map((layer, layerIndex) => {
var mapConfigLayer = mapConfig.layers[layerIndex];
if (mapConfigLayer.options && mapConfigLayer.options.widgets) {
layer.widgets = layer.widgets || {};
Object.keys(mapConfigLayer.options.widgets).forEach((widgetName) => {
var resource = layergroup.layergroupid + '/' + layerIndex + '/widget/' + widgetName;
layer.widgets[widgetName] = {
type: mapConfigLayer.options.widgets[widgetName].type,
url: this.resourceLocator.getUrls(username, resource)
};
});
}
return layer;
});
}
}
addAnalysesMetadata (username, layergroup, analysesResults, includeQuery) {
includeQuery = includeQuery || false;
analysesResults = analysesResults || [];
layergroup.metadata.analyses = [];
analysesResults.forEach((analysis) => {
var nodes = analysis.getNodes();
layergroup.metadata.analyses.push({
nodes: nodes.reduce((nodesIdMap, node) => {
if (node.params.id) {
var nodeResource = layergroup.layergroupid + '/analysis/node/' + node.id();
var nodeRepr = {
status: node.getStatus(),
url: this.resourceLocator.getUrls(username, nodeResource)
};
if (includeQuery) {
nodeRepr.query = node.getQuery();
}
if (node.getStatus() === 'failed') {
nodeRepr.error_message = node.getErrorMessage();
}
nodesIdMap[node.params.id] = nodeRepr;
}
return nodesIdMap;
}, {})
});
});
}
addAggregationContextMetadata (layergroup, mapConfig, context) {
if (layergroup.metadata && Array.isArray(layergroup.metadata.layers) && Array.isArray(mapConfig.layers)) {
layergroup.metadata.layers = layergroup.metadata.layers.map(function(layer, layerIndex) {
if (context.aggregation && Array.isArray(context.aggregation.layers)) {
layer.meta.aggregation = context.aggregation.layers[layerIndex];
}
return layer;
});
}
}
addTileJsonMetadata (layergroup, user, mapconfig) {
const isVectorOnlyMapConfig = mapconfig.isVectorOnlyMapConfig();
let hasMapnikLayers = false;
layergroup.metadata.layers.forEach((layerMetadata, index) => {
const layerId = mapconfig.getLayerId(index);
const rasterResource = `${layergroup.layergroupid}/${layerId}/{z}/{x}/{y}.png`;
if (mapconfig.layerType(index) === 'mapnik') {
hasMapnikLayers = true;
const vectorResource = `${layergroup.layergroupid}/${layerId}/{z}/{x}/{y}.mvt`;
const layerTilejson = {
vector: this._getTilejson(this.resourceLocator.getTileUrls(user, vectorResource))
};
if (!isVectorOnlyMapConfig) {
let grids = null;
const layer = mapconfig.getLayer(index);
if (layer.options.interactivity) {
const gridResource = `${layergroup.layergroupid}/${layerId}/{z}/{x}/{y}.grid.json`;
grids = this.resourceLocator.getTileUrls(user, gridResource);
}
layerTilejson.raster = this._getTilejson(
this.resourceLocator.getTileUrls(user, rasterResource),
grids
);
}
layerMetadata.tilejson = layerTilejson;
} else {
layerMetadata.tilejson = {
raster: this._getTilejson(this.resourceLocator.getTileUrls(user, rasterResource))
};
}
});
const tilejson = {};
const url = {};
if (hasMapnikLayers) {
const vectorResource = `${layergroup.layergroupid}/{z}/{x}/{y}.mvt`;
tilejson.vector = this._getTilejson(
this.resourceLocator.getTileUrls(user, vectorResource)
);
url.vector = this._getTemplateUrl(this.resourceLocator.getTemplateUrls(user, vectorResource));
if (!isVectorOnlyMapConfig) {
const rasterResource = `${layergroup.layergroupid}/{z}/{x}/{y}.png`;
tilejson.raster = this._getTilejson(
this.resourceLocator.getTileUrls(user, rasterResource)
);
url.raster = this._getTemplateUrl(this.resourceLocator.getTemplateUrls(user, rasterResource));
}
}
layergroup.metadata.tilejson = tilejson;
layergroup.metadata.url = url;
}
_getTilejson(tiles, grids) {
const tilejson = {
tilejson: '2.2.0',
tiles: tiles.https || tiles.http
};
if (grids) {
tilejson.grids = grids.https || grids.http;
}
return tilejson;
}
_getTemplateUrl(url) {
return url.https || url.http;
}
addTurboCartoContextMetadata(layergroup, mapConfig, context) {
if (layergroup.metadata && Array.isArray(layergroup.metadata.layers) && Array.isArray(mapConfig.layers)) {
layergroup.metadata.layers = layergroup.metadata.layers.map(function(layer, layerIndex) {
if (context.turboCarto && Array.isArray(context.turboCarto.layers)) {
layer.meta.cartocss_meta = context.turboCarto.layers[layerIndex];
}
return layer;
});
}
}
};

View File

@@ -1,7 +1,7 @@
{
"private": true,
"name": "windshaft-cartodb",
"version": "5.2.0",
"version": "5.4.0",
"description": "A map tile server for CartoDB",
"keywords": [
"cartodb"
@@ -24,11 +24,12 @@
"Simon Martin <simon@carto.com>"
],
"dependencies": {
"basic-auth": "^2.0.0",
"body-parser": "^1.18.2",
"camshaft": "0.60.0",
"camshaft": "0.61.4",
"cartodb-psql": "0.10.2",
"cartodb-query-tables": "0.3.0",
"cartodb-redis": "0.14.0",
"cartodb-redis": "0.16.0",
"debug": "^3.1.0",
"dot": "~1.0.2",
"express": "~4.16.0",
@@ -40,14 +41,14 @@
"node-statsd": "~0.0.7",
"on-headers": "^1.0.1",
"queue-async": "~1.0.7",
"redis-mpool": "0.4.1",
"request": "^2.83.0",
"redis-mpool": "0.5.0",
"request": "2.85.0",
"semver": "~5.3.0",
"step": "~0.0.6",
"step-profiler": "~0.3.0",
"turbo-carto": "0.20.2",
"underscore": "~1.6.0",
"windshaft": "4.3.3",
"windshaft": "4.6.0",
"yargs": "~5.0.0"
},
"devDependencies": {
@@ -56,7 +57,7 @@
"mocha": "~3.4.1",
"moment": "~2.18.1",
"nock": "~2.11.0",
"redis": "~0.12.1",
"redis": "2.8.0",
"strftime": "~0.8.2"
},
"scripts": {

View File

@@ -1438,6 +1438,43 @@ describe('aggregation', function () {
done();
});
});
it('aggregation should work with attributes', function (done) {
this.mapConfig = createVectorMapConfig([
{
type: 'cartodb',
options: {
sql: POINTS_SQL_1,
cartocss: '#layer { marker-width: 7; }',
cartocss_version: '2.3.0',
aggregation: {
threshold: 1
},
attributes: {
id: 'cartodb_id',
columns: [
'value'
]
}
}
}
]);
this.testClient = new TestClient(this.mapConfig);
this.testClient.getLayergroup((err, body) => {
if (err) {
return done(err);
}
assert.equal(typeof body.metadata, 'object');
assert.ok(Array.isArray(body.metadata.layers));
body.metadata.layers.forEach(layer => assert.ok(layer.meta.aggregation.mvt));
body.metadata.layers.forEach(layer => assert.ok(layer.meta.aggregation.png));
done();
});
});
});
});
});

View File

@@ -188,7 +188,7 @@ describe('analysis-layers error cases', function() {
]
);
var testClient = new TestClient(mapConfig, 11111);
var testClient = new TestClient(mapConfig); //No apikey provided -> using default public apikey
testClient.getLayergroup({ response: AUTH_ERROR_RESPONSE }, function(err, layergroupResult) {
assert.ok(!err, err);

View File

@@ -0,0 +1,181 @@
//Remove this file when Auth fallback is not used anymore
// AUTH_FALLBACK
const assert = require('../../support/assert');
const testHelper = require('../../support/test_helper');
const CartodbWindshaft = require('../../../lib/cartodb/server');
const serverOptions = require('../../../lib/cartodb/server_options');
const server = new CartodbWindshaft(serverOptions);
var LayergroupToken = require('../../../lib/cartodb/models/layergroup-token');
function singleLayergroupConfig(sql, cartocss) {
return {
version: '1.7.0',
layers: [
{
type: 'mapnik',
options: {
sql: sql,
cartocss: cartocss,
cartocss_version: '2.3.0'
}
}
]
};
}
function createRequest(layergroup, userHost, apiKey) {
var url = layergroupUrl;
if (apiKey) {
url += '?api_key=' + apiKey;
}
return {
url: url,
method: 'POST',
headers: {
host: userHost || 'localhost',
'Content-Type': 'application/json'
},
data: JSON.stringify(layergroup)
};
}
var layergroupUrl = '/api/v1/map';
var pointSqlMaster = "select * from test_table_private_1";
var pointSqlPublic = "select * from test_table";
var keysToDelete;
describe('authorization fallback', function () {
beforeEach(function () {
keysToDelete = {};
});
afterEach(function (done) {
testHelper.deleteRedisKeys(keysToDelete, done);
});
it("succeed with master", function (done) {
var layergroup = singleLayergroupConfig(pointSqlMaster, '#layer { marker-fill:red; }');
assert.response(server,
createRequest(layergroup, 'user_previous_to_project_auth', '4444'),
{
status: 200
},
function (res, err) {
assert.ifError(err);
var parsed = JSON.parse(res.body);
assert.ok(parsed.layergroupid);
assert.equal(res.headers['x-layergroup-id'], parsed.layergroupid);
keysToDelete['map_cfg|' + LayergroupToken.parse(parsed.layergroupid).token] = 0;
keysToDelete['user:user_previous_to_project_auth:mapviews:global'] = 5;
done();
}
);
});
it("succeed with default - sending default_public", function (done) {
var layergroup = singleLayergroupConfig(pointSqlPublic, '#layer { marker-fill:red; }');
assert.response(server,
createRequest(layergroup, 'user_previous_to_project_auth', 'default_public'),
{
status: 200
},
function (res, err) {
assert.ifError(err);
var parsed = JSON.parse(res.body);
assert.ok(parsed.layergroupid);
assert.equal(res.headers['x-layergroup-id'], parsed.layergroupid);
keysToDelete['map_cfg|' + LayergroupToken.parse(parsed.layergroupid).token] = 0;
keysToDelete['user:user_previous_to_project_auth:mapviews:global'] = 5;
done();
}
);
});
it("succeed with default - sending no api key token", function (done) {
var layergroup = singleLayergroupConfig(pointSqlPublic, '#layer { marker-fill:red; }');
assert.response(server,
createRequest(layergroup, 'user_previous_to_project_auth'),
{
status: 200
},
function (res, err) {
assert.ifError(err);
var parsed = JSON.parse(res.body);
assert.ok(parsed.layergroupid);
assert.equal(res.headers['x-layergroup-id'], parsed.layergroupid);
keysToDelete['map_cfg|' + LayergroupToken.parse(parsed.layergroupid).token] = 0;
keysToDelete['user:user_previous_to_project_auth:mapviews:global'] = 5;
done();
}
);
});
it("succeed with non-existent api key - defaults to default", function (done) {
var layergroup = singleLayergroupConfig(pointSqlPublic, '#layer { marker-fill:red; }');
assert.response(server,
createRequest(layergroup, 'user_previous_to_project_auth', 'THIS-API-KEY-DOESNT-EXIST'),
{
status: 200
},
function (res, err) {
assert.ifError(err);
var parsed = JSON.parse(res.body);
assert.ok(parsed.layergroupid);
assert.equal(res.headers['x-layergroup-id'], parsed.layergroupid);
keysToDelete['map_cfg|' + LayergroupToken.parse(parsed.layergroupid).token] = 0;
keysToDelete['user:user_previous_to_project_auth:mapviews:global'] = 5;
done();
}
);
});
it("fail with default", function (done) {
var layergroup = singleLayergroupConfig(pointSqlMaster, '#layer { marker-fill:red; }');
assert.response(server,
createRequest(layergroup, 'user_previous_to_project_auth', 'default_public'),
{
status: 403
},
function (res, err) {
assert.ifError(err);
done();
}
);
});
it("fail with non-existent api key - defaults to default", function (done) {
var layergroup = singleLayergroupConfig(pointSqlMaster, '#layer { marker-fill:red; }');
assert.response(server,
createRequest(layergroup, 'user_previous_to_project_auth', 'THIS-API-KEY-DOESNT-EXIST'),
{
status: 403
},
function (res, err) {
assert.ifError(err);
done();
}
);
});
});

View File

@@ -0,0 +1,431 @@
require('../../support/test_helper');
const assert = require('../../support/assert');
const TestClient = require('../../support/test-client');
const mapnik = require('windshaft').mapnik;
const PERMISSION_DENIED_RESPONSE = {
status: 403,
headers: {
'Content-Type': 'application/json; charset=utf-8'
}
};
describe('authorization', function() {
it('should create a layergroup with regular apikey token', function(done) {
const apikeyToken = 'regular1';
const mapConfig = {
version: '1.7.0',
layers: [
{
options: {
sql: 'select * FROM test_table_localhost_regular1',
cartocss: TestClient.CARTOCSS.POINTS,
cartocss_version: '2.3.0'
}
}
]
};
const testClient = new TestClient(mapConfig, apikeyToken);
testClient.getLayergroup(function (err, layergroupResult) {
assert.ifError(err);
assert.ok(layergroupResult.layergroupid);
testClient.drain(done);
});
});
it('should create and get a named map tile using a regular apikey token', function (done) {
const apikeyToken = 'regular1';
const mapConfig = {
version: '1.7.0',
layers: [
{
options: {
sql: 'select * FROM test_table_localhost_regular1',
cartocss: TestClient.CARTOCSS.POINTS,
cartocss_version: '2.3.0'
}
}
]
};
const testClient = new TestClient(mapConfig, apikeyToken);
testClient.getTile(0, 0, 0, function (err, res, tile) {
assert.ifError(err);
assert.equal(res.statusCode, 200);
assert.ok(tile instanceof mapnik.Image);
testClient.drain(done);
});
});
it('should fail getting a named map tile with default apikey token', function (done) {
const apikeyTokenCreate = 'regular1';
const apikeyTokenGet = 'default_public';
const mapConfig = {
version: '1.7.0',
layers: [
{
options: {
sql: 'select * FROM test_table_localhost_regular1',
cartocss: TestClient.CARTOCSS.POINTS,
cartocss_version: '2.3.0'
}
}
]
};
const testClientCreate = new TestClient(mapConfig, apikeyTokenCreate);
testClientCreate.getLayergroup(function (err, layergroupResult) {
assert.ifError(err);
const layergroupId = layergroupResult.layergroupid;
const testClientGet = new TestClient({}, apikeyTokenGet);
const params = {
layergroupid: layergroupId,
response: PERMISSION_DENIED_RESPONSE
};
testClientGet.getTile(0, 0, 0, params, function(err, res, body) {
assert.ifError(err);
assert.ok(body.hasOwnProperty('errors'));
assert.equal(body.errors.length, 1);
assert.ok(body.errors[0].match(/permission denied/), body.errors[0]);
testClientGet.drain(done);
});
});
});
it('should fail creating a layergroup with default apikey token', function (done) {
const apikeyToken = 'default_public';
const mapConfig = {
version: '1.7.0',
layers: [
{
options: {
sql: 'select * FROM test_table_localhost_regular1',
cartocss: TestClient.CARTOCSS.POINTS,
cartocss_version: '2.3.0'
}
}
]
};
const testClient = new TestClient(mapConfig, apikeyToken);
testClient.getLayergroup({ response: { status: 403 } }, function (err, layergroupResult) {
assert.ifError(err);
assert.ok(layergroupResult.hasOwnProperty('errors'));
assert.equal(layergroupResult.errors.length, 1);
assert.ok(layergroupResult.errors[0].match(/permission denied/), layergroupResult.errors[0]);
testClient.drain(done);
});
});
it('should create a layergroup with default apikey token', function (done) {
const apikeyToken = 'default_public';
const mapConfig = {
version: '1.7.0',
layers: [
{
options: {
sql: 'select * FROM test_table',
cartocss: TestClient.CARTOCSS.POINTS,
cartocss_version: '2.3.0'
}
}
]
};
const testClient = new TestClient(mapConfig, apikeyToken);
testClient.getLayergroup(function (err, layergroupResult) {
assert.ifError(err);
assert.ok(layergroupResult.layergroupid);
testClient.drain(done);
});
});
it('should create and get a tile with default apikey token', function (done) {
const apikeyToken = 'default_public';
const mapConfig = {
version: '1.7.0',
layers: [
{
options: {
sql: 'select * FROM test_table',
cartocss: TestClient.CARTOCSS.POINTS,
cartocss_version: '2.3.0'
}
}
]
};
const testClient = new TestClient(mapConfig, apikeyToken);
testClient.getTile(0, 0, 0, function (err, res, tile) {
assert.ifError(err);
assert.equal(res.statusCode, 200);
assert.ok(tile instanceof mapnik.Image);
testClient.drain(done);
});
});
it('should fail if apikey does not grant access to table', function (done) {
const mapConfig = {
version: '1.7.0',
layers: [
{
options: {
sql: 'select * FROM test_table_localhost_regular1',
cartocss: TestClient.CARTOCSS.POINTS,
cartocss_version: '2.3.0'
}
}
]
};
const testClient = new TestClient(mapConfig); //no apikey provided, using default
testClient.getLayergroup({ response: { status: 403 } }, function (err, layergroupResult) { //TODO 401
assert.ifError(err);
assert.ok(layergroupResult.hasOwnProperty('errors'));
assert.equal(layergroupResult.errors.length, 1);
assert.ok(layergroupResult.errors[0].match(/permission denied/), layergroupResult.errors[0]);
testClient.drain(done);
});
});
it('should forbide access to API if API key does not grant access', function (done) {
const apikeyToken = 'regular2';
const mapConfig = {
version: '1.7.0',
layers: [
{
options: {
sql: 'select * FROM test_table_localhost_regular1',
cartocss: TestClient.CARTOCSS.POINTS,
cartocss_version: '2.3.0'
}
}
]
};
const testClient = new TestClient(mapConfig, apikeyToken);
testClient.getLayergroup({ response: { status: 403 } }, function (err, layergroupResult) {
assert.ifError(err);
assert.ok(layergroupResult.hasOwnProperty('errors'));
assert.equal(layergroupResult.errors.length, 1);
assert.ok(layergroupResult.errors[0].match(/Forbidden/), layergroupResult.errors[0]);
testClient.drain(done);
});
});
it('should create a layergroup with a source analysis using a default apikey token', function (done) {
const apikeyToken = 'default_public';
const mapConfig = {
version: '1.7.0',
layers: [
{
type: 'cartodb',
options: {
source: {
id: 'HEAD'
},
cartocss: TestClient.CARTOCSS.POINTS,
cartocss_version: '2.3.0'
}
}
],
analyses: [
{
id: 'HEAD',
type: 'source',
params: {
query: 'select * from populated_places_simple_reduced'
}
}
]
};
const testClient = new TestClient(mapConfig, apikeyToken);
testClient.getLayergroup(function (err, layergroupResult) {
assert.ifError(err);
assert.ok(layergroupResult.layergroupid);
testClient.drain(done);
});
});
it('should create a layergroup with a source analysis using a regular apikey token', function (done) {
const apikeyToken = 'regular1';
const mapConfig = {
version: '1.7.0',
layers: [
{
type: 'cartodb',
options: {
source: {
id: 'HEAD'
},
cartocss: TestClient.CARTOCSS.POINTS,
cartocss_version: '2.3.0'
}
}
],
analyses: [
{
id: 'HEAD',
type: 'source',
params: {
query: 'select * from test_table_localhost_regular1'
}
}
]
};
const testClient = new TestClient(mapConfig, apikeyToken);
testClient.getLayergroup(function (err, layergroupResult) {
assert.ifError(err);
assert.ok(layergroupResult.layergroupid);
testClient.drain(done);
});
});
// Warning: TBA
it('should create a layergroup with a buffer analysis using a regular apikey token', function (done) {
const apikeyToken = 'regular1';
const mapConfig = {
version: '1.7.0',
layers: [
{
type: 'cartodb',
options: {
source: {
id: 'HEAD1'
},
cartocss: TestClient.CARTOCSS.POINTS,
cartocss_version: '2.3.0'
}
}
],
analyses: [
{
id: "HEAD1",
type: "buffer",
params: {
source: {
id: 'HEAD2',
type: 'source',
params: {
query: 'select * from test_table_localhost_regular1'
}
},
radius: 50000
}
}
]
};
const testClient = new TestClient(mapConfig, apikeyToken);
testClient.getLayergroup(function (err, layergroupResult) {
assert.ifError(err);
assert.ok(layergroupResult.layergroupid);
testClient.drain(done);
});
});
it('should create and get a named map tile using a regular apikey token', function (done) {
const apikeyToken = 'regular1';
const template = {
version: '0.0.1',
name: 'auth-api-template',
placeholders: {
buffersize: {
type: 'number',
default: 0
}
},
layergroup: {
version: '1.7.0',
layers: [{
type: 'cartodb',
options: {
sql: 'select * from test_table_localhost_regular1',
cartocss: TestClient.CARTOCSS.POINTS,
cartocss_version: '2.3.0',
}
}]
}
};
const testClient = new TestClient(template, apikeyToken);
testClient.getTile(0, 0, 0, function (err, res, tile) {
assert.ifError(err);
assert.equal(res.statusCode, 200);
assert.ok(tile instanceof mapnik.Image);
testClient.drain(done);
});
});
it('should fail creating a named map using a regular apikey token and a private table', function (done) {
const apikeyToken = 'regular1';
const template = {
version: '0.0.1',
name: 'auth-api-template-private',
placeholders: {
buffersize: {
type: 'number',
default: 0
}
},
layergroup: {
version: '1.7.0',
layers: [{
type: 'cartodb',
options: {
sql: 'select * from populated_places_simple_reduced_private',
cartocss: TestClient.CARTOCSS.POINTS,
cartocss_version: '2.3.0',
}
}]
}
};
const testClient = new TestClient(template, apikeyToken);
testClient.getTile(0, 0, 0, { response: PERMISSION_DENIED_RESPONSE }, function (err, res, body) {
assert.ifError(err);
assert.ok(body.hasOwnProperty('errors'));
assert.equal(body.errors.length, 1);
assert.ok(body.errors[0].match(/permission denied/), body.errors[0]);
testClient.drain(done);
});
});
});

View File

@@ -155,6 +155,7 @@ describe('get requests with cache headers', function() {
assert.ok(res.headers['x-cache-channel']);
assert.ok(res.headers['surrogate-key']);
assert.equal(res.headers.vary, 'Authorization');
if (expectedCacheHeaders) {
validateXChannelHeaders(res.headers, expectedCacheHeaders);
assert.equal(res.headers['surrogate-key'], expectedCacheHeaders.surrogate_keys);

View File

@@ -70,12 +70,8 @@ describe('aggregations happy cases', function() {
].join(' UNION ALL ');
operations.forEach(function (operation) {
var not = operation === 'count' ? ' not ' : ' ';
var description = 'should' +
not +
'handle NULL values in category and aggregation columns using "' +
operation +
'" as aggregation operation';
var description = 'should handle NULL values in category and aggregation columns using "' +
operation + '" as aggregation operation';
it(description, function (done) {
this.testClient = new TestClient(aggregationOperationMapConfig(operation, query, 'cat', 'val'));
@@ -96,12 +92,7 @@ describe('aggregations happy cases', function() {
}
});
if (operation === 'count') {
assert.ok(hasNullCategory, 'aggregation has not a category NULL');
} else {
assert.ok(!hasNullCategory, 'aggregation has category NULL');
}
assert.ok(!hasNullCategory, 'aggregation has category NULL');
done();
});
});
@@ -425,3 +416,79 @@ describe('aggregation dataview tuned by categories query param', function () {
});
});
});
describe('Count aggregation', function () {
const mapConfig = {
version: '1.5.0',
layers: [
{
type: "cartodb",
options: {
source: {
"id": "a0"
},
cartocss: "#points { marker-width: 10; marker-fill: red; }",
cartocss_version: "2.3.0"
}
}
],
dataviews: {
categories: {
source: {
id: 'a0'
},
type: 'aggregation',
options: {
column: 'cat',
aggregation: 'count'
}
}
},
analyses: [
{
id: "a0",
type: "source",
params: {
query: `
SELECT
null::geometry the_geom_webmercator,
CASE
WHEN x % 4 = 0 THEN 1
WHEN x % 4 = 1 THEN 2
WHEN x % 4 = 2 THEN 3
ELSE null
END AS val,
CASE
WHEN x % 4 = 0 THEN 'category_1'
WHEN x % 4 = 1 THEN 'category_2'
WHEN x % 4 = 2 THEN 'category_3'
ELSE null
END AS cat
FROM generate_series(1, 1000) x
`
}
}
]
};
it(`should handle null values correctly when aggregationColumn isn't provided`, function (done) {
this.testClient = new TestClient(mapConfig, 1234);
this.testClient.getDataview('categories', { own_filter: 0, categories: 0 }, (err, dataview) => {
assert.ifError(err);
assert.equal(dataview.categories.length, 3);
this.testClient.drain(done);
});
});
it(`should handle null values correctly when aggregationColumn is provided`, function (done) {
mapConfig.dataviews.categories.options.aggregationColumn = 'val';
this.testClient = new TestClient(mapConfig, 1234);
this.testClient.getDataview('categories', { own_filter: 0, categories: 0 }, (err, dataview) => {
assert.ifError(err);
assert.equal(dataview.categories.length, 3);
this.testClient.drain(done);
});
});
});

View File

@@ -34,6 +34,48 @@ return function () {
serverOptions.renderer.mvt.usePostGIS = originalUsePostGIS;
});
describe('named map tile', function () {
it('should get default named vector tile', function (done) {
const apikeyToken = 1234;
const templateName = `mvt-template-${usePostGIS ? 'postgis' : 'mapnik'}`;
const template = {
version: '0.0.1',
name: templateName,
placeholders: {
buffersize: {
type: 'number',
default: 0
}
},
layergroup: {
version: '1.7.0',
layers: [{
type: 'cartodb',
options: {
sql: 'select * from populated_places_simple_reduced limit 10',
cartocss: TestClient.CARTOCSS.POINTS,
cartocss_version: '2.3.0',
}
}]
}
};
const testClient = new TestClient(template, apikeyToken);
testClient.getNamedTile(templateName, 0, 0, 0, 'mvt', {}, (err, res, tile) => {
if (err) {
return done(err);
}
const tileJSON = tile.toJSON();
assert.equal(tileJSON[0].features.length, 10);
testClient.drain(done);
});
});
});
describe('analysis-layers-dataviews-mvt', function () {
function createMapConfig(layers, dataviews, analysis) {

View File

@@ -125,7 +125,7 @@ describe('attributes', function() {
var parsed = JSON.parse(res.body);
assert.ok(parsed.errors);
var msg = parsed.errors[0];
assert.ok(msg.match(/0 features.*identified by fid -666/), msg);
assert.equal(msg, "Multiple features (0) identified by 'i' = -666 in layer 1");
return null;
},
function finish(err) {

View File

@@ -27,6 +27,7 @@ module.exports = _.extend({}, serverOptions, {
snapToGrid: false,
clipByBox2d: false, // this requires postgis >=2.2 and geos >=3.5
scale_factors: [1, 2],
metrics: false,
limits: {
render: 0,
cacheOnTimeout: true

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -131,6 +131,19 @@ HMSET rails:users:cartodb250user id ${TESTUSERID} \
map_key 4321
EOF
# Remove this block when Auth fallback is not used anymore
# AUTH_FALLBACK
# A user to test auth fallback to no api keys mode
cat <<EOF | redis-cli -p ${REDIS_PORT} -n 5
HMSET rails:users:user_previous_to_project_auth id ${TESTUSERID} \
database_name "${TEST_DB}" \
database_host "localhost" \
database_password "${TESTPASS}" \
database_publicuser "${PUBLICUSER}"\
map_key 4444
EOF
cat <<EOF | redis-cli -p ${REDIS_PORT} -n 0
HSET rails:${TEST_DB}:my_table infowindow "this, that, the other"
HSET rails:${TEST_DB}:test_table_private_1 privacy "0"
@@ -138,4 +151,77 @@ EOF
fi
# API keys ==============================
# User localhost -----------------------
# API Key Master
cat <<EOF | redis-cli -p ${REDIS_PORT} -n 5
HMSET api_keys:localhost:1234 \
user "localhost" \
type "master" \
grants_sql "true" \
grants_maps "true" \
database_role "${TESTUSER}" \
database_password "${TESTPASS}"
EOF
# API Key Default public
cat <<EOF | redis-cli -p ${REDIS_PORT} -n 5
HMSET api_keys:localhost:default_public \
user "localhost" \
type "default" \
grants_sql "true" \
grants_maps "true" \
database_role "test_windshaft_publicuser" \
database_password "public"
EOF
# API Key Regular
cat <<EOF | redis-cli -p ${REDIS_PORT} -n 5
HMSET api_keys:localhost:regular1 \
user "localhost" \
type "regular" \
grants_sql "true" \
grants_maps "true" \
database_role "test_windshaft_regular1" \
database_password "regular1"
EOF
# API Key Regular 2 no Maps API access, only to check grants permissions to the API
cat <<EOF | redis-cli -p ${REDIS_PORT} -n 5
HMSET api_keys:localhost:regular2 \
user "localhost" \
type "regular" \
grants_sql "true" \
grants_maps "false" \
database_role "test_windshaft_publicuser" \
database_password "public"
EOF
# User cartodb250user -----------------------
# API Key Master
cat <<EOF | redis-cli -p ${REDIS_PORT} -n 5
HMSET api_keys:cartodb250user:4321 \
user "localhost" \
type "master" \
grants_sql "true" \
grants_maps "true" \
database_role "${TESTUSER}" \
database_password "${TESTPASS}"
EOF
# API Key Default
cat <<EOF | redis-cli -p ${REDIS_PORT} -n 5
HMSET api_keys:cartodb250user:default_public \
user "localhost" \
type "default" \
grants_sql "true" \
grants_maps "true" \
database_role "test_windshaft_publicuser" \
database_password "public"
EOF
echo "Finished preparing data. Ready to run tests"

View File

@@ -23,6 +23,12 @@ CREATE USER :PUBLICUSER WITH PASSWORD ':PUBLICPASS';
DROP USER IF EXISTS :TESTUSER;
CREATE USER :TESTUSER WITH PASSWORD ':TESTPASS';
-- regular user role 1
DROP USER IF EXISTS test_windshaft_regular1;
CREATE USER test_windshaft_regular1 WITH PASSWORD 'regular1';
GRANT test_windshaft_regular1 to :TESTUSER;
-- first table
CREATE TABLE test_table (
updated_at timestamp without time zone DEFAULT now(),
@@ -189,6 +195,7 @@ INSERT INTO CDB_TableMetadata (tabname, updated_at) VALUES ('test_table_private_
-- GRANT SELECT ON CDB_TableMetadata TO :PUBLICUSER;
GRANT SELECT ON CDB_TableMetadata TO :TESTUSER;
GRANT SELECT ON CDB_TableMetadata TO test_windshaft_regular1; -- for analysis. Warning: TBA
-- long name table
CREATE TABLE
@@ -412,6 +419,52 @@ INSERT INTO _vovw_1_test_special_float_values_table_overviews VALUES
(3, 'El Rey del Tallarín', 'Plaza Conde de Toreno 2, Madrid, Spain', 'NaN'::float, '0101000020E610000021C8410933AD0DC0CB0EF10F5B364440', '0101000020110F000053E71AC64D3419C10F664E4659CC5241', 1),
(4, 'El Lacón', 'Manuel Fernández y González 8, Madrid, Spain', 'infinity'::float, '0101000020E6100000BC5983F755990DC07D923B6C22354440', '0101000020110F00005DACDB056F2319C1EC41A980FCCA5241', 2);
-- auth tables --------------------------------------------
CREATE TABLE test_table_localhost_regular1 (
updated_at timestamp without time zone DEFAULT now(),
created_at timestamp without time zone DEFAULT now(),
cartodb_id integer NOT NULL,
name character varying,
address character varying,
the_geom geometry,
the_geom_webmercator geometry,
CONSTRAINT enforce_dims_the_geom CHECK ((st_ndims(the_geom) = 2)),
CONSTRAINT enforce_dims_the_geom_webmercator CHECK ((st_ndims(the_geom_webmercator) = 2)),
CONSTRAINT enforce_geotype_the_geom CHECK (((geometrytype(the_geom) = 'POINT'::text) OR (the_geom IS NULL))),
CONSTRAINT enforce_geotype_the_geom_webmercator CHECK (((geometrytype(the_geom_webmercator) = 'POINT'::text) OR (the_geom_webmercator IS NULL))),
CONSTRAINT enforce_srid_the_geom CHECK ((st_srid(the_geom) = 4326)),
CONSTRAINT enforce_srid_the_geom_webmercator CHECK ((st_srid(the_geom_webmercator) = 3857))
);
CREATE SEQUENCE test_table_localhost_regular1_cartodb_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE test_table_localhost_regular1_cartodb_id_seq OWNED BY test_table_localhost_regular1.cartodb_id;
SELECT pg_catalog.setval('test_table_localhost_regular1_cartodb_id_seq', 60, true);
ALTER TABLE test_table_localhost_regular1 ALTER COLUMN cartodb_id SET DEFAULT nextval('test_table_localhost_regular1_cartodb_id_seq'::regclass);
INSERT INTO test_table_localhost_regular1 VALUES
('2011-09-21 14:02:21.358706', '2011-09-21 14:02:21.314252', 1, 'Hawai', 'Calle de Pérez Galdós 9, Madrid, Spain', '0101000020E6100000A6B73F170D990DC064E8D84125364440', '0101000020110F000076491621312319C122D4663F1DCC5241'),
('2011-09-21 14:02:21.358706', '2011-09-21 14:02:21.319101', 2, 'El Estocolmo', 'Calle de la Palma 72, Madrid, Spain', '0101000020E6100000C90567F0F7AB0DC0AB07CC43A6364440', '0101000020110F0000C4356B29423319C15DD1092DADCC5241'),
('2011-09-21 14:02:21.358706', '2011-09-21 14:02:21.324', 3, 'El Rey del Tallarín', 'Plaza Conde de Toreno 2, Madrid, Spain', '0101000020E610000021C8410933AD0DC0CB0EF10F5B364440', '0101000020110F000053E71AC64D3419C10F664E4659CC5241'),
('2011-09-21 14:02:21.358706', '2011-09-21 14:02:21.329509', 4, 'El Lacón', 'Manuel Fernández y González 8, Madrid, Spain', '0101000020E6100000BC5983F755990DC07D923B6C22354440', '0101000020110F00005DACDB056F2319C1EC41A980FCCA5241'),
('2011-09-21 14:02:21.358706', '2011-09-21 14:02:21.334931', 5, 'El Pico', 'Calle Divino Pastor 12, Madrid, Spain', '0101000020E61000003B6D8D08C6A10DC0371B2B31CF364440', '0101000020110F00005F716E91992A19C17DAAA4D6DACC5241');
ALTER TABLE ONLY test_table_localhost_regular1 ADD CONSTRAINT test_table_localhost_regular1_pkey PRIMARY KEY (cartodb_id);
CREATE INDEX test_table_localhost_regular1_the_geom_idx ON test_table_localhost_regular1 USING gist (the_geom);
CREATE INDEX test_table_localhost_regular1_the_geom_webmercator_idx ON test_table_localhost_regular1 USING gist (the_geom_webmercator);
GRANT ALL ON TABLE test_table_localhost_regular1 TO :TESTUSER;
GRANT ALL ON TABLE test_table_localhost_regular1 TO test_windshaft_regular1;
-- analysis tables -----------------------------------------------
ALTER TABLE cdb_analysis_catalog OWNER TO :TESTUSER;
@@ -705,6 +758,7 @@ GRANT SELECT ON TABLE analysis_rent_listings TO :PUBLICUSER;
--
GRANT SELECT, UPDATE, INSERT, DELETE ON cdb_analysis_catalog TO :TESTUSER;
GRANT SELECT, UPDATE, INSERT, DELETE ON cdb_analysis_catalog TO test_windshaft_regular1; -- for analysis. Warning: TBA
DROP EXTENSION IF EXISTS crankshaft;
CREATE SCHEMA IF NOT EXISTS cdb_crankshaft;

View File

@@ -414,7 +414,7 @@ TestClient.prototype.getDataview = function(dataviewName, params, callback) {
var urlParams = {};
if (params.hasOwnProperty('no_filters')) {
urlParams.no_filters = params.no_filters;
}
}
if (params.hasOwnProperty('own_filter')) {
urlParams.own_filter = params.own_filter;
}
@@ -1253,3 +1253,87 @@ TestClient.prototype.getAnalysesCatalog = function (params, callback) {
}
);
};
TestClient.prototype.getNamedTile = function (name, z, x, y, format, options, callback) {
const { params } = options;
if (!this.apiKey) {
return callback(new Error('apiKey param is mandatory to create a new template'));
}
const createTemplateRequest = {
url: `/api/v1/map/named?${qs.stringify({ api_key: this.apiKey })}`,
method: 'POST',
headers: {
host: 'localhost',
'Content-Type': 'application/json'
},
data: JSON.stringify(this.template)
};
const createTemplateResponse = {
status: 200,
headers: {
'Content-Type': 'application/json; charset=utf-8'
}
};
assert.response(this.server, createTemplateRequest, createTemplateResponse, (res, err) => {
if (err) {
return callback(err);
}
const templateId = JSON.parse(res.body).template_id;
const queryParams = params ? `?${qs.stringify(params)}` : '';
const url = `/api/v1/map/named/${templateId}/all/${[z,x,y].join('/')}.${format}${queryParams}`;
const namedTileRequest = {
url,
method: 'GET',
headers: {
host: 'localhost'
},
encoding: 'binary'
};
let contentType;
switch (format) {
case 'png':
contentType = 'image/png';
break;
case 'mvt':
contentType = 'application/x-protobuf';
break;
default:
contentType = 'application/json';
break;
}
const namedTileResponse = Object.assign({
status: 200,
headers: {
'content-type': contentType
}
}, options.response);
assert.response(this.server, namedTileRequest, namedTileResponse, (res, err) => {
let body;
switch (res.headers['content-type']) {
case 'image/png':
body = mapnik.Image.fromBytes(new Buffer(res.body, 'binary'));
break;
case 'application/x-protobuf':
body = new mapnik.VectorTile(z, x, y);
body.setDataSync(new Buffer(res.body, 'binary'));
break;
case 'application/json; charset=utf-8':
body = JSON.parse(res.body);
break;
default:
body = res.body;
break;
}
return callback(err, res, body);
});
});
};

View File

@@ -113,7 +113,14 @@ afterEach(function(done) {
'rails:test_windshaft_cartodb_user_1_db:my_table': true,
'rails:users:localhost:map_key': true,
'rails:users:cartodb250user': true,
'rails:users:localhost': true
'rails:users:localhost': true,
'rails:users:user_previous_to_project_auth': true, // AUTH_FALLBACK
'api_keys:localhost:1234': true,
'api_keys:localhost:default_public': true,
'api_keys:cartodb250user:4321': true,
'api_keys:cartodb250user:default_public': true,
'api_keys:localhost:regular1': true,
'api_keys:localhost:regular2': true,
};
var databasesTasks = { 0: 'users', 5: 'meta'};

View File

@@ -12,6 +12,7 @@ describe('lzma-middleware', function() {
}
};
testHelper.lzma_compress_to_base64(JSON.stringify(qo), 1, function(err, data) {
const lzma = lzmaMiddleware();
var req = {
headers: {
host:'localhost'
@@ -19,9 +20,13 @@ describe('lzma-middleware', function() {
query: {
api_key: 'test',
lzma: data
},
profiler: {
done: function () {}
}
};
lzmaMiddleware(req, {}, function(err) {
lzma(req, {}, function(err) {
if ( err ) {
return done(err);
}

View File

@@ -1,90 +0,0 @@
require('../../../support/test_helper.js');
var assert = require('assert');
var LayergroupController = require('../../../../lib/cartodb/controllers/layergroup');
describe('tile stats', function() {
beforeEach(function () {
this.statsClient = global.statsClient;
});
afterEach(function() {
global.statsClient = this.statsClient;
});
it('finalizeGetTileOrGrid does not call statsClient when format is not supported', function() {
var expectedCalls = 2, // it will call increment once for the general error
invalidFormat = 'png2',
invalidFormatRegexp = new RegExp('invalid'),
formatMatched = false;
mockStatsClientGetInstance({
increment: function(label) {
formatMatched = formatMatched || !!label.match(invalidFormatRegexp);
expectedCalls--;
}
});
var layergroupController = new LayergroupController();
var reqMock = {
profiler: { toJSONString:function() {} },
params: {
format: invalidFormat
}
};
var resMock = {
status: function() { return this; },
set: function() {},
json: function() {},
jsonp: function() {},
send: function() {}
};
var next = function () {};
layergroupController.finalizeGetTileOrGrid('Unsupported format png2', reqMock, resMock, null, null, next);
assert.ok(formatMatched, 'Format was never matched in increment method');
assert.equal(expectedCalls, 0, 'Unexpected number of calls to increment method');
});
it('finalizeGetTileOrGrid calls statsClient when format is supported', function() {
var expectedCalls = 2, // general error + format error
validFormat = 'png',
validFormatRegexp = new RegExp(validFormat),
formatMatched = false;
mockStatsClientGetInstance({
increment: function(label) {
formatMatched = formatMatched || !!label.match(validFormatRegexp);
expectedCalls--;
}
});
var reqMock = {
profiler: { toJSONString:function() {} },
params: {
format: validFormat
}
};
var resMock = {
status: function() { return this; },
set: function() {},
json: function() {},
jsonp: function() {},
send: function() {}
};
var layergroupController = new LayergroupController();
var next = function () {};
layergroupController.finalizeGetTileOrGrid('Another error happened', reqMock, resMock, null, null, next);
assert.ok(formatMatched, 'Format was never matched in increment method');
assert.equal(expectedCalls, 0, 'Unexpected number of calls to increment method');
});
function mockStatsClientGetInstance(instance) {
global.statsClient = Object.assign(global.statsClient, instance);
}
});

View File

@@ -10,6 +10,7 @@ var TemplateMaps = require('../../../lib/cartodb/backends/template_maps');
const cleanUpQueryParamsMiddleware = require('../../../lib/cartodb/middleware/context/clean-up-query-params');
const authorizeMiddleware = require('../../../lib/cartodb/middleware/context/authorize');
const dbConnSetupMiddleware = require('../../../lib/cartodb/middleware/context/db-conn-setup');
const credentialsMiddleware = require('../../../lib/cartodb/middleware/context/credentials');
const localsMiddleware = require('../../../lib/cartodb/middleware/context/locals');
var windshaft = require('windshaft');
@@ -23,6 +24,7 @@ describe('prepare-context', function() {
let cleanUpQueryParams;
let dbConnSetup;
let authorize;
let setCredentials;
before(function() {
var redisPool = new RedisPool(global.environment.redis);
@@ -35,6 +37,7 @@ describe('prepare-context', function() {
cleanUpQueryParams = cleanUpQueryParamsMiddleware();
authorize = authorizeMiddleware(authApi);
dbConnSetup = dbConnSetupMiddleware(pgConnection);
setCredentials = credentialsMiddleware();
});
@@ -58,20 +61,23 @@ describe('prepare-context', function() {
}
res.locals.user = 'localhost';
res.set = function () {};
return res;
}
it('res.locals are created', function(done) {
const locals = localsMiddleware();
let req = {};
let res = {};
localsMiddleware(prepareRequest(req), prepareResponse(res), function(err) {
locals(prepareRequest(req), prepareResponse(res), function(err) {
if ( err ) { done(err); return; }
assert.ok(res.hasOwnProperty('locals'), 'response has locals');
done();
});
});
it('cleans up request', function(done){
var req = {headers: { host:'localhost' }, query: {dbuser:'hacker',dbname:'secret'}};
var res = {};
@@ -103,8 +109,20 @@ describe('prepare-context', function() {
});
it('sets also dbuser for authenticated requests', function(done){
var req = { headers: { host: 'localhost' }, query: { map_key: '1234' }};
var res = { set: function () {} };
var req = {
headers: {
host: 'localhost'
},
query: {
api_key: '1234'
}
};
var res = {
set: function () {},
locals: {
api_key: '1234'
}
};
// FIXME: review authorize-pgconnsetup workflow, It might we are doing authorization twice.
authorize(prepareRequest(req), prepareResponse(res), function (err) {
@@ -154,7 +172,7 @@ describe('prepare-context', function() {
}
};
var res = {};
cleanUpQueryParams(prepareRequest(req), prepareResponse(res), function (err) {
if ( err ) {
return done(err);
@@ -168,4 +186,66 @@ describe('prepare-context', function() {
});
});
describe('Set apikey token', function(){
it('from query param', function (done) {
var req = {
headers: {
host: 'localhost'
},
query: {
api_key: '1234',
}
};
var res = {};
setCredentials(prepareRequest(req), prepareResponse(res), function (err) {
if (err) {
return done(err);
}
var query = res.locals;
assert.equal('1234', query.api_key);
done();
});
});
it('from body param', function (done) {
var req = {
headers: {
host: 'localhost'
},
body: {
api_key: '1234',
}
};
var res = {};
setCredentials(prepareRequest(req), prepareResponse(res), function (err) {
if (err) {
return done(err);
}
var query = res.locals;
assert.equal('1234', query.api_key);
done();
});
});
it('from http header', function (done) {
var req = {
headers: {
host: 'localhost',
authorization: 'Basic bG9jYWxob3N0OjEyMzQ=', // user: localhost, password: 1234
}
};
var res = {};
setCredentials(prepareRequest(req), prepareResponse(res), function (err) {
if (err) {
return done(err);
}
var query = res.locals;
assert.equal('1234', query.api_key);
done();
});
});
});
});

224
yarn.lock
View File

@@ -2,20 +2,20 @@
# yarn lockfile v1
"@carto/mapnik@3.6.2-carto.2", "@carto/mapnik@~3.6.2-carto.0":
version "3.6.2-carto.2"
resolved "https://registry.yarnpkg.com/@carto/mapnik/-/mapnik-3.6.2-carto.2.tgz#45a055fd2d39530a873ef9ce5a325baacc81c196"
"@carto/mapnik@3.6.2-carto.4":
version "3.6.2-carto.4"
resolved "https://registry.yarnpkg.com/@carto/mapnik/-/mapnik-3.6.2-carto.4.tgz#54042a5dbea293c54e1bd286b32277694c5dc2d2"
dependencies:
mapnik-vector-tile "1.5.0"
nan "~2.7.0"
node-pre-gyp "~0.6.30"
protozero "1.5.1"
"@carto/tilelive-bridge@github:cartodb/tilelive-bridge#2.5.1-cdb1":
version "2.5.1-cdb1"
resolved "https://codeload.github.com/cartodb/tilelive-bridge/tar.gz/b0b5559f948e77b337bc9a9ae0bf6ec4249fba21"
"@carto/tilelive-bridge@cartodb/tilelive-bridge#2.5.1-cdb4":
version "2.5.1-cdb4"
resolved "https://codeload.github.com/cartodb/tilelive-bridge/tar.gz/3eb554e5109199f50f457cec72ee288cffa5d6b3"
dependencies:
"@carto/mapnik" "~3.6.2-carto.0"
"@carto/mapnik" "3.6.2-carto.4"
"@mapbox/sphericalmercator" "~1.0.1"
mapnik-pool "~0.1.3"
@@ -23,19 +23,15 @@
version "1.0.5"
resolved "https://registry.yarnpkg.com/@mapbox/sphericalmercator/-/sphericalmercator-1.0.5.tgz#70237b9774095ed1cfdbcea7a8fd1fc82b2691f2"
"abaculus@github:cartodb/abaculus#2.0.3-cdb2":
version "2.0.3-cdb2"
resolved "https://codeload.github.com/cartodb/abaculus/tar.gz/6468e0e3fddb2b23f60b9a3156117cff0307f6dc"
abaculus@cartodb/abaculus#2.0.3-cdb5:
version "2.0.3-cdb5"
resolved "https://codeload.github.com/cartodb/abaculus/tar.gz/b899cbea04b3e6093aa3ef32331920acd5f839a1"
dependencies:
"@carto/mapnik" "~3.6.2-carto.0"
"@carto/mapnik" "3.6.2-carto.4"
d3-queue "^2.0.2"
sphericalmercator "1.0.x"
abbrev@1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
abbrev@1.0.x:
abbrev@1, abbrev@1.0.x:
version "1.0.9"
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135"
@@ -147,6 +143,12 @@ balanced-match@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
basic-auth@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-2.0.0.tgz#015db3f353e02e56377755f962742e8981e7bbba"
dependencies:
safe-buffer "5.1.1"
bcrypt-pbkdf@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz#63bc5dcb61331b92bc05fd528953c33462a06f8d"
@@ -236,18 +238,18 @@ camelcase@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a"
camshaft@0.60.0:
version "0.60.0"
resolved "https://registry.yarnpkg.com/camshaft/-/camshaft-0.60.0.tgz#0433b5a576e08cabbc9bae1e1b22305274b8b7b6"
camshaft@0.61.4:
version "0.61.4"
resolved "https://registry.yarnpkg.com/camshaft/-/camshaft-0.61.4.tgz#eeab7e69a07d6fbc39ebc01ad19acf979de8fb2a"
dependencies:
async "^1.5.2"
bunyan "1.8.1"
cartodb-psql "^0.10.1"
debug "^3.1.0"
dot "^1.0.3"
request "^2.69.0"
request "2.85.0"
"canvas@github:cartodb/node-canvas#1.6.2-cdb2":
canvas@cartodb/node-canvas#1.6.2-cdb2:
version "1.6.2-cdb2"
resolved "https://codeload.github.com/cartodb/node-canvas/tar.gz/8acf04557005c633f9e68524488a2657c04f3766"
dependencies:
@@ -265,15 +267,15 @@ carto@0.16.3:
semver "^5.1.0"
yargs "^4.2.0"
"carto@github:cartodb/carto#0.15.1-cdb1":
carto@CartoDB/carto#0.15.1-cdb1:
version "0.15.1-cdb1"
resolved "https://codeload.github.com/cartodb/carto/tar.gz/8050ec843f1f32a6469e5d1cf49602773015d398"
resolved "https://codeload.github.com/CartoDB/carto/tar.gz/8050ec843f1f32a6469e5d1cf49602773015d398"
dependencies:
mapnik-reference "~6.0.2"
optimist "~0.6.0"
underscore "~1.6.0"
"carto@github:cartodb/carto#0.15.1-cdb3":
carto@cartodb/carto#0.15.1-cdb3:
version "0.15.1-cdb3"
resolved "https://codeload.github.com/cartodb/carto/tar.gz/945f5efb74fd1af1f5e1f69f409f9567f94fb5a7"
dependencies:
@@ -299,12 +301,12 @@ cartodb-query-tables@0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/cartodb-query-tables/-/cartodb-query-tables-0.3.0.tgz#56e18d869666eb2e8e2cb57d0baf3acc923f8756"
cartodb-redis@0.14.0:
version "0.14.0"
resolved "https://registry.yarnpkg.com/cartodb-redis/-/cartodb-redis-0.14.0.tgz#6f82fdb3e5b7c8005dbaccd6172c1706c4378df2"
cartodb-redis@0.16.0:
version "0.16.0"
resolved "https://registry.yarnpkg.com/cartodb-redis/-/cartodb-redis-0.16.0.tgz#969312fd329b24a76bf6e5a4dd961445f2eda734"
dependencies:
dot "~1.0.2"
redis-mpool "~0.4.1"
redis-mpool "^0.5.0"
underscore "~1.6.0"
caseless@~0.12.0:
@@ -549,14 +551,14 @@ domutils@1.5:
dom-serializer "0"
domelementtype "1"
dot@^1.0.3:
version "1.1.2"
resolved "https://registry.yarnpkg.com/dot/-/dot-1.1.2.tgz#c7377019fc4e550798928b2b9afeb66abfa1f2f9"
dot@~1.0.2:
dot@^1.0.3, dot@~1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/dot/-/dot-1.0.3.tgz#f8750bfb6b03c7664eb0e6cb1eb4c66419af9427"
double-ended-queue@^2.1.0-0:
version "2.1.0-0"
resolved "https://registry.yarnpkg.com/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz#103d3527fd31528f40188130c841efdd78264e5c"
dtrace-provider@~0.6:
version "0.6.0"
resolved "https://registry.yarnpkg.com/dtrace-provider/-/dtrace-provider-0.6.0.tgz#0b078d5517937d873101452d9146737557b75e51"
@@ -677,14 +679,10 @@ extend@~3.0.0, extend@~3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444"
extsprintf@1.3.0:
extsprintf@1.3.0, extsprintf@^1.2.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
extsprintf@^1.2.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
fast-deep-equal@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff"
@@ -791,7 +789,7 @@ gdal@~0.9.2:
nan "~2.6.2"
node-pre-gyp "~0.6.36"
generic-pool@2.4.3:
generic-pool@2.4.3, generic-pool@~2.4.0, generic-pool@~2.4.1:
version "2.4.3"
resolved "https://registry.yarnpkg.com/generic-pool/-/generic-pool-2.4.3.tgz#780c36f69dfad05a5a045dd37be7adca11a4f6ff"
@@ -803,10 +801,6 @@ generic-pool@~2.2.0, generic-pool@~2.2.1:
version "2.2.2"
resolved "https://registry.yarnpkg.com/generic-pool/-/generic-pool-2.2.2.tgz#7a89f491d575b42f9f069a0e8e2c6dbaa3c241be"
generic-pool@~2.4.0, generic-pool@~2.4.1:
version "2.4.6"
resolved "https://registry.yarnpkg.com/generic-pool/-/generic-pool-2.4.6.tgz#f1b55e572167dba2fe75d5aa91ebb1e9f72642d7"
get-caller-file@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5"
@@ -1257,8 +1251,8 @@ lodash@3.7.x:
resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.7.0.tgz#3678bd8ab995057c07ade836ed2ef087da811d45"
lodash@^4.5.1:
version "4.17.4"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
version "4.17.5"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511"
log4js@cartodb/log4js-node#cdb:
version "0.6.25"
@@ -1347,9 +1341,9 @@ mime@~1.2.11:
version "1.2.11"
resolved "https://registry.yarnpkg.com/mime/-/mime-1.2.11.tgz#58203eed86e3a5ef17aed2b7d9ebd47f0a60dd10"
mime@~1.3.4:
version "1.3.6"
resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.6.tgz#591d84d3653a6b0b4a3b9df8de5aa8108e72e5e0"
mime@~1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
"minimatch@2 || 3", minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.4, minimatch@~3.0.2:
version "3.0.4"
@@ -1357,7 +1351,7 @@ mime@~1.3.4:
dependencies:
brace-expansion "^1.1.7"
minimist@0.0.8:
minimist@0.0.8, minimist@~0.0.1:
version "0.0.8"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
@@ -1365,10 +1359,6 @@ minimist@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
minimist@~0.0.1:
version "0.0.10"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf"
minimist@~0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.2.0.tgz#4dffe525dae2b864c66c2e23c6271d7afdecefce"
@@ -1395,11 +1385,7 @@ mocha@~3.4.1:
mkdirp "0.5.1"
supports-color "3.1.2"
moment@^2.10.6:
version "2.20.1"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.20.1.tgz#d6eb1a46cbcc14a2b2f9434112c1ff8907f313fd"
moment@~2.18.1:
moment@^2.10.6, moment@~2.18.1:
version "2.18.1"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.18.1.tgz#c36193dd3ce1c2eed2adb7c802dbbc77a81b1c0f"
@@ -1513,14 +1499,10 @@ oauth-sign@~0.8.1, oauth-sign@~0.8.2:
version "0.8.2"
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43"
object-assign@4.1.0:
object-assign@4.1.0, object-assign@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.0.tgz#7a3b3d0e98063d43f4c03f2e8ae6cd51a86883a0"
object-assign@^4.1.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
object-keys@~0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-0.4.0.tgz#28a6aae7428dd2c3a92f3d95f21335dd204e0336"
@@ -1649,7 +1631,7 @@ pg-types@1.*:
postgres-date "~1.0.0"
postgres-interval "^1.1.0"
"pg@github:cartodb/node-postgres#6.1.6-cdb1":
pg@cartodb/node-postgres#6.1.6-cdb1:
version "6.1.6"
resolved "https://codeload.github.com/cartodb/node-postgres/tar.gz/3eef52dd1e655f658a4ee8ac5697688b3ecfed44"
dependencies:
@@ -1817,7 +1799,7 @@ read-pkg@^1.0.0:
normalize-package-data "^2.3.2"
path-type "^1.0.0"
readable-stream@1.1:
readable-stream@1.1, readable-stream@~1.1.9:
version "1.1.13"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.13.tgz#f6eef764f514c89e2b9e23146a75ba106756d23e"
dependencies:
@@ -1847,27 +1829,30 @@ readable-stream@~1.0.2:
isarray "0.0.1"
string_decoder "~0.10.x"
readable-stream@~1.1.9:
version "1.1.14"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9"
dependencies:
core-util-is "~1.0.0"
inherits "~2.0.1"
isarray "0.0.1"
string_decoder "~0.10.x"
redis-commands@^1.2.0:
version "1.3.1"
resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.3.1.tgz#81d826f45fa9c8b2011f4cd7a0fe597d241d442b"
redis-mpool@0.4.1, redis-mpool@~0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/redis-mpool/-/redis-mpool-0.4.1.tgz#d917c0a4ed57a1291a9c6eb35434e6c0b7046f80"
redis-mpool@0.5.0, redis-mpool@^0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/redis-mpool/-/redis-mpool-0.5.0.tgz#9a22dcffb4ad796ec88ce3038b991deae9d1fda7"
dependencies:
generic-pool "~2.1.1"
hiredis "~0.5.0"
redis "~0.12.1"
redis "^2.8.0"
underscore "~1.6.0"
redis@~0.12.1:
version "0.12.1"
resolved "https://registry.yarnpkg.com/redis/-/redis-0.12.1.tgz#64df76ad0fc8acebaebd2a0645e8a48fac49185e"
redis-parser@^2.6.0:
version "2.6.0"
resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-2.6.0.tgz#52ed09dacac108f1a631c07e9b69941e7a19504b"
redis@2.8.0, redis@^2.8.0:
version "2.8.0"
resolved "https://registry.yarnpkg.com/redis/-/redis-2.8.0.tgz#202288e3f58c49f6079d97af7a10e1303ae14b02"
dependencies:
double-ended-queue "^2.1.0-0"
redis-commands "^1.2.0"
redis-parser "^2.6.0"
repeat-string@^1.5.2:
version "1.6.1"
@@ -1900,7 +1885,34 @@ request@2.81.0:
tunnel-agent "^0.6.0"
uuid "^3.0.0"
request@2.x, request@^2.55.0, request@^2.69.0, request@^2.83.0:
request@2.85.0:
version "2.85.0"
resolved "https://registry.yarnpkg.com/request/-/request-2.85.0.tgz#5a03615a47c61420b3eb99b7dba204f83603e1fa"
dependencies:
aws-sign2 "~0.7.0"
aws4 "^1.6.0"
caseless "~0.12.0"
combined-stream "~1.0.5"
extend "~3.0.1"
forever-agent "~0.6.1"
form-data "~2.3.1"
har-validator "~5.0.3"
hawk "~6.0.2"
http-signature "~1.2.0"
is-typedarray "~1.0.0"
isstream "~0.1.2"
json-stringify-safe "~5.0.1"
mime-types "~2.1.17"
oauth-sign "~0.8.2"
performance-now "^2.1.0"
qs "~6.5.1"
safe-buffer "^5.1.1"
stringstream "~0.0.5"
tough-cookie "~2.3.3"
tunnel-agent "^0.6.0"
uuid "^3.1.0"
request@2.x, request@^2.55.0:
version "2.83.0"
resolved "https://registry.yarnpkg.com/request/-/request-2.83.0.tgz#ca0b65da02ed62935887808e6f510381034e3356"
dependencies:
@@ -1965,9 +1977,9 @@ safe-json-stringify@~1:
version "1.0.4"
resolved "https://registry.yarnpkg.com/safe-json-stringify/-/safe-json-stringify-1.0.4.tgz#81a098f447e4bbc3ff3312a243521bc060ef5911"
"semver@2 || 3 || 4 || 5", semver@^5.1.0, semver@^5.3.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab"
"semver@2 || 3 || 4 || 5", semver@^5.1.0, semver@^5.3.0, semver@~5.3.0:
version "5.3.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f"
semver@4.3.2:
version "4.3.2"
@@ -1981,10 +1993,6 @@ semver@~5.0.3:
version "5.0.3"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.0.3.tgz#77466de589cd5d3c95f138aa78bc569a3cb5d27a"
semver@~5.3.0:
version "5.3.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f"
send@0.16.1:
version "0.16.1"
resolved "https://registry.yarnpkg.com/send/-/send-0.16.1.tgz#a70e1ca21d1382c11d0d9f6231deb281080d7ab3"
@@ -2082,14 +2090,10 @@ speedometer@~0.1.2:
version "0.1.4"
resolved "https://registry.yarnpkg.com/speedometer/-/speedometer-0.1.4.tgz#9876dbd2a169d3115402d48e6ea6329c8816a50d"
sphericalmercator@1.0.4:
sphericalmercator@1.0.4, sphericalmercator@1.0.x, sphericalmercator@~1.0.1, sphericalmercator@~1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/sphericalmercator/-/sphericalmercator-1.0.4.tgz#baad4e34187f06e87f2e92fc1280199fa1b01d4e"
sphericalmercator@1.0.x, sphericalmercator@~1.0.1, sphericalmercator@~1.0.4:
version "1.0.5"
resolved "https://registry.yarnpkg.com/sphericalmercator/-/sphericalmercator-1.0.5.tgz#ddc5a049e360e000d0fad9fc22c4071882584980"
split@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/split/-/split-1.0.1.tgz#605bd9be303aa59fb35f9229fbea0ddec9ea07d9"
@@ -2127,11 +2131,7 @@ sshpk@^1.7.0:
jsbn "~0.1.0"
tweetnacl "~0.14.0"
"statuses@>= 1.3.1 < 2":
version "1.4.0"
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087"
statuses@~1.3.1:
"statuses@>= 1.3.1 < 2", statuses@~1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e"
@@ -2239,13 +2239,13 @@ through@2:
version "2.3.8"
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
"tilelive-mapnik@github:cartodb/tilelive-mapnik#0.6.18-cdb4":
version "0.6.18-cdb4"
resolved "https://codeload.github.com/cartodb/tilelive-mapnik/tar.gz/510cfb6f033f7551f973886751643202d4cb5f4a"
tilelive-mapnik@cartodb/tilelive-mapnik#0.6.18-cdb8:
version "0.6.18-cdb8"
resolved "https://codeload.github.com/cartodb/tilelive-mapnik/tar.gz/9cb4546c8fdd34ced0a41dbf70e143475b4e2067"
dependencies:
"@carto/mapnik" "~3.6.2-carto.0"
"@carto/mapnik" "3.6.2-carto.4"
generic-pool "~2.4.0"
mime "~1.3.4"
mime "~1.6.0"
sphericalmercator "~1.0.4"
step "~0.0.5"
@@ -2400,13 +2400,13 @@ window-size@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.2.0.tgz#b4315bb4214a3d7058ebeee892e13fa24d98b075"
windshaft@4.3.3:
version "4.3.3"
resolved "https://registry.yarnpkg.com/windshaft/-/windshaft-4.3.3.tgz#a48fdd6ca05257c103f34c80195722ef52dc0813"
windshaft@4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/windshaft/-/windshaft-4.6.0.tgz#d9394aff73c0aa761207ad2b0f12d1c23ac41244"
dependencies:
"@carto/mapnik" "3.6.2-carto.2"
"@carto/tilelive-bridge" cartodb/tilelive-bridge#2.5.1-cdb1
abaculus cartodb/abaculus#2.0.3-cdb2
"@carto/mapnik" "3.6.2-carto.4"
"@carto/tilelive-bridge" cartodb/tilelive-bridge#2.5.1-cdb4
abaculus cartodb/abaculus#2.0.3-cdb5
canvas cartodb/node-canvas#1.6.2-cdb2
carto cartodb/carto#0.15.1-cdb3
cartodb-psql "^0.10.1"
@@ -2414,13 +2414,13 @@ windshaft@4.3.3:
dot "~1.0.2"
grainstore "1.8.2"
queue-async "~1.0.7"
redis-mpool "0.4.1"
request "^2.83.0"
redis-mpool "^0.5.0"
request "2.85.0"
semver "~5.0.3"
sphericalmercator "1.0.4"
step "~0.0.6"
tilelive "5.12.2"
tilelive-mapnik cartodb/tilelive-mapnik#0.6.18-cdb4
tilelive-mapnik cartodb/tilelive-mapnik#0.6.18-cdb8
torque.js "~2.11.0"
underscore "~1.6.0"