udata 9.1.5.dev31391__py2.py3-none-any.whl → 9.2.2.dev31715__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of udata might be problematic. Click here for more details.
- tasks/__init__.py +1 -1
- udata/__init__.py +1 -1
- udata/api/commands.py +10 -2
- udata/api/oauth2.py +4 -3
- udata/api_fields.py +5 -1
- udata/core/dataservices/models.py +2 -2
- udata/core/dataset/csv.py +4 -16
- udata/core/discussions/api.py +24 -1
- udata/core/discussions/csv.py +22 -0
- udata/core/owned.py +16 -0
- udata/core/reuse/api.py +3 -2
- udata/core/reuse/models.py +1 -1
- udata/cors.py +9 -4
- udata/static/chunks/{5.3aa55302c802f48db900.js → 5.9049c2001a2f21930f78.js} +2 -2
- udata/static/chunks/5.9049c2001a2f21930f78.js.map +1 -0
- udata/static/chunks/{6.0db5d3ff22944de7edd5.js → 6.ad092769b0983a6eec2a.js} +19 -19
- udata/static/chunks/6.ad092769b0983a6eec2a.js.map +1 -0
- udata/static/common.js +1 -1
- udata/static/common.js.map +1 -1
- udata/tests/api/test_auth_api.py +88 -0
- udata/tests/api/test_reuses_api.py +105 -0
- udata/tests/test_cors.py +37 -1
- udata/tests/test_discussions.py +61 -0
- udata/tests/test_owned.py +100 -1
- {udata-9.1.5.dev31391.dist-info → udata-9.2.2.dev31715.dist-info}/METADATA +18 -3
- {udata-9.1.5.dev31391.dist-info → udata-9.2.2.dev31715.dist-info}/RECORD +30 -29
- udata/static/chunks/5.3aa55302c802f48db900.js.map +0 -1
- udata/static/chunks/6.0db5d3ff22944de7edd5.js.map +0 -1
- {udata-9.1.5.dev31391.dist-info → udata-9.2.2.dev31715.dist-info}/LICENSE +0 -0
- {udata-9.1.5.dev31391.dist-info → udata-9.2.2.dev31715.dist-info}/WHEEL +0 -0
- {udata-9.1.5.dev31391.dist-info → udata-9.2.2.dev31715.dist-info}/entry_points.txt +0 -0
- {udata-9.1.5.dev31391.dist-info → udata-9.2.2.dev31715.dist-info}/top_level.txt +0 -0
udata/static/common.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
!function(e){function __webpack_require__(a){if(c[a])return c[a].exports;var r=c[a]={exports:{},id:a,loaded:!1};return e[a].call(r.exports,r,r.exports,__webpack_require__),r.loaded=!0,r.exports}var a=window.webpackJsonp;window.webpackJsonp=function(b,f){for(var t,
|
|
1
|
+
!function(e){function __webpack_require__(a){if(c[a])return c[a].exports;var r=c[a]={exports:{},id:a,loaded:!1};return e[a].call(r.exports,r,r.exports,__webpack_require__),r.loaded=!0,r.exports}var a=window.webpackJsonp;window.webpackJsonp=function(b,f){for(var t,_,n=0,d=[];n<b.length;n++)_=b[n],r[_]&&d.push.apply(d,r[_]),r[_]=0;for(t in f){var i=f[t];switch(typeof i){case"object":e[t]=function(a){var c=a.slice(1),r=a[0];return function(a,b,f){e[r].apply(this,[a,b,f].concat(c))}}(i);break;case"function":e[t]=i;break;default:e[t]=e[i]}}for(a&&a(b,f);d.length;)d.shift().call(null,__webpack_require__);if(f[0])return c[0]=0,__webpack_require__(0)};var c={},r={31:0};__webpack_require__.e=function(e,a){if(0===r[e])return a.call(null,__webpack_require__);if(void 0!==r[e])r[e].push(a);else{r[e]=[a];var c=document.getElementsByTagName("head")[0],b=document.createElement("script");b.type="text/javascript",b.charset="utf-8",b.async=!0,b.src=__webpack_require__.p+"chunks/"+e+"."+{0:"285786eca84d7b7471ac",1:"765e28a442160395e71a",2:"f6372652ce6895fe1fad",3:"31f4515503a814e0e3a3",4:"ef9b48820b09477986b5",5:"9049c2001a2f21930f78",6:"ad092769b0983a6eec2a",7:"b2e2b3f88a04f6b296dd",8:"292c6a157d71f0b21b6f",9:"8ad948dd393d38f07a7a",10:"c1c9496ebfc8949f3de2",11:"18f1cc16362b76373c40",12:"f6b6e770dfe4a5949444",13:"066b4d36814efa6c485f",14:"72a6fc22c9b0cc8bcd14",15:"7de380008f4ec6ea4b5b",16:"cddb601c07a2df6564b2",17:"26a29ec18e773c2be9b2",18:"b7098bfe5401f75973fa",19:"2061dca8438f415029a3",20:"17e9e766625991aa0a7b",21:"2b48c7e5fe59775d5eb3",22:"2a919b7f6014457264e8",23:"18bb4231e440e17f29a2",24:"bb29ce250a1b77491420",25:"6cb865e768a122d52b4d",26:"2bd61452982a0d4de0a3",27:"1872ae081edb64216589",28:"1e240391cc0358805408",29:"556152a9334761b0d972",30:"a2eb870c6d29644ed9b5",32:"92a026dcad120ccdf15f",33:"6636c1fd8aec6fc072b8"}[e]+".js",c.appendChild(b)}},__webpack_require__.m=e,__webpack_require__.c=c,__webpack_require__.p="/static/"}(function(e){for(var a in e)if(Object.prototype.hasOwnProperty.call(e,a))switch(typeof e[a]){case"function":break;case"object":e[a]=function(a){var c=a.slice(1),r=e[a[0]];return function(e,a,b){r.apply(this,[e,a,b].concat(c))}}(e[a]);break;default:e[a]=e[e[a]]}return e}([]));
|
|
2
2
|
//# sourceMappingURL=common.js.map
|
udata/static/common.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["webpack:///common.js","webpack:///webpack/bootstrap aae90f74dc2315755797"],"names":["modules","__webpack_require__","moduleId","installedModules","exports","module","id","loaded","call","parentJsonpFunction","window","chunkIds","moreModules","chunkId","i","callbacks","length","installedChunks","push","apply","_m","args","slice","templateId","a","b","c","this","concat","shift","31","e","callback","undefined","head","document","getElementsByTagName","script","createElement","type","charset","async","src","p","0","1","2","3","4","5","6","7","8","9","10","11","12","13","14","15","16","17","18","19","20","21","22","23","24","25","26","27","28","29","30","32","33","appendChild","m","Object","prototype","hasOwnProperty","fn"],"mappings":"CAAS,SAAUA,GCwDnB,QAAAC,qBAAAC,GAGA,GAAAC,EAAAD,GACA,MAAAC,GAAAD,GAAAE,OAGA,IAAAC,GAAAF,EAAAD,IACAE,WACAE,GAAAJ,EACAK,QAAA,EAUA,OANAP,GAAAE,GAAAM,KAAAH,EAAAD,QAAAC,IAAAD,QAAAH,qBAGAI,EAAAE,QAAA,EAGAF,EAAAD,QA3EA,GAAAK,GAAAC,OAAA,YACAA,QAAA,sBAAAC,EAAAC,GAIA,IADA,GAAAV,GAAAW,EAAAC,EAAA,EAAAC,KACQD,EAAAH,EAAAK,OAAoBF,IAC5BD,EAAAF,EAAAG,GACAG,EAAAJ,IACAE,EAAAG,KAAAC,MAAAJ,EAAAE,EAAAJ,IACAI,EAAAJ,GAAA,CAEA,KAAAX,IAAAU,GAAA,CACA,GAAAQ,GAAAR,EAAAV,EAGA,cAAAkB,IACA,aAEApB,EAAAE,GAAA,SAAAkB,GACA,GAAAC,GAAAD,EAAAE,MAAA,GAAAC,EAAAH,EAAA,EACA,iBAAAI,EAAAC,EAAAC,GACA1B,EAAAuB,GAAAJ,MAAAQ,MAAAH,EAAAC,EAAAC,GAAAE,OAAAP,MAEMD,EACN,MACA,gBAEApB,EAAAE,GAAAkB,CACA,MACA,SAEApB,EAAAE,GAAAF,EAAAoB,IAKA,IADAX,KAAAE,EAAAC,GACAG,EAAAC,QACAD,EAAAc,QAAArB,KAAA,KAAAP,oBACA,IAAAW,EAAA,GAEA,MADAT,GAAA,KACAF,oBAAA,GAKA,IAAAE,MAKAc,GACAa,GAAA,EA6BA7B,qBAAA8B,EAAA,SAAAlB,EAAAmB,GAEA,OAAAf,EAAAJ,GACA,MAAAmB,GAAAxB,KAAA,KAAAP,oBAGA,IAAAgC,SAAAhB,EAAAJ,GACAI,EAAAJ,GAAAK,KAAAc,OACI,CAEJf,EAAAJ,IAAAmB,EACA,IAAAE,GAAAC,SAAAC,qBAAA,WACAC,EAAAF,SAAAG,cAAA,SACAD,GAAAE,KAAA,kBACAF,EAAAG,QAAA,QACAH,EAAAI,OAAA,EAEAJ,EAAAK,IAAAzC,oBAAA0C,EAAA,UAAA9B,EAAA,KAAsE+B,EAAA,uBAAAC,EAAA,uBAAAC,EAAA,uBAAAC,EAAA,uBAAAC,EAAA,uBAAAC,EAAA,uBAAAC,EAAA,uBAAAC,EAAA,uBAAAC,EAAA,uBAAAC,EAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,wBAAk5B/D,GAAA,MACx9BqB,EAAA2C,YAAAxC,KAKApC,oBAAA6E,EAAA9E,EAGAC,oBAAAyB,EAAAvB,EAGAF,oBAAA0C,EAAA,YDIW,SAAS3C,GAEnB,IAAI,GAAIc,KAAKd,GACZ,GAAG+E,OAAOC,UAAUC,eAAezE,KAAKR,EAASc,GAChD,aAAcd,GAAQc,IACtB,IAAK,WAAY,KACjB,KAAK,SAEJd,EAAQc,GAAM,SAASM,GACtB,GAAIC,GAAOD,EAAGE,MAAM,GAAI4D,EAAKlF,EAAQoB,EAAG,GACxC,OAAO,UAAUI,EAAEC,EAAEC,GACpBwD,EAAG/D,MAAMQ,MAAOH,EAAEC,EAAEC,GAAGE,OAAOP,MAE9BrB,EAAQc,GACV,MACD,SAECd,EAAQc,GAAKd,EAAQA,EAAQc,IAKhC,MAAOd","file":"common.js","sourcesContent":["/******/ (function(modules) { // webpackBootstrap\n/******/ \t// install a JSONP callback for chunk loading\n/******/ \tvar parentJsonpFunction = window[\"webpackJsonp\"];\n/******/ \twindow[\"webpackJsonp\"] = function webpackJsonpCallback(chunkIds, moreModules) {\n/******/ \t\t// add \"moreModules\" to the modules object,\n/******/ \t\t// then flag all \"chunkIds\" as loaded and fire callback\n/******/ \t\tvar moduleId, chunkId, i = 0, callbacks = [];\n/******/ \t\tfor(;i < chunkIds.length; i++) {\n/******/ \t\t\tchunkId = chunkIds[i];\n/******/ \t\t\tif(installedChunks[chunkId])\n/******/ \t\t\t\tcallbacks.push.apply(callbacks, installedChunks[chunkId]);\n/******/ \t\t\tinstalledChunks[chunkId] = 0;\n/******/ \t\t}\n/******/ \t\tfor(moduleId in moreModules) {\n/******/ \t\t\tvar _m = moreModules[moduleId];\n/******/\n/******/ \t\t\t// Check if module is deduplicated\n/******/ \t\t\tswitch(typeof _m) {\n/******/ \t\t\tcase \"object\":\n/******/ \t\t\t\t// Module can be created from a template\n/******/ \t\t\t\tmodules[moduleId] = (function(_m) {\n/******/ \t\t\t\t\tvar args = _m.slice(1), templateId = _m[0];\n/******/ \t\t\t\t\treturn function (a,b,c) {\n/******/ \t\t\t\t\t\tmodules[templateId].apply(this, [a,b,c].concat(args));\n/******/ \t\t\t\t\t};\n/******/ \t\t\t\t}(_m));\n/******/ \t\t\t\tbreak;\n/******/ \t\t\tcase \"function\":\n/******/ \t\t\t\t// Normal module\n/******/ \t\t\t\tmodules[moduleId] = _m;\n/******/ \t\t\t\tbreak;\n/******/ \t\t\tdefault:\n/******/ \t\t\t\t// Module is a copy of another module\n/******/ \t\t\t\tmodules[moduleId] = modules[_m];\n/******/ \t\t\t\tbreak;\n/******/ \t\t\t}\n/******/ \t\t}\n/******/ \t\tif(parentJsonpFunction) parentJsonpFunction(chunkIds, moreModules);\n/******/ \t\twhile(callbacks.length)\n/******/ \t\t\tcallbacks.shift().call(null, __webpack_require__);\n/******/ \t\tif(moreModules[0]) {\n/******/ \t\t\tinstalledModules[0] = 0;\n/******/ \t\t\treturn __webpack_require__(0);\n/******/ \t\t}\n/******/ \t};\n/******/\n/******/ \t// The module cache\n/******/ \tvar installedModules = {};\n/******/\n/******/ \t// object to store loaded and loading chunks\n/******/ \t// \"0\" means \"already loaded\"\n/******/ \t// Array means \"loading\", array contains callbacks\n/******/ \tvar installedChunks = {\n/******/ \t\t31:0\n/******/ \t};\n/******/\n/******/ \t// The require function\n/******/ \tfunction __webpack_require__(moduleId) {\n/******/\n/******/ \t\t// Check if module is in cache\n/******/ \t\tif(installedModules[moduleId])\n/******/ \t\t\treturn installedModules[moduleId].exports;\n/******/\n/******/ \t\t// Create a new module (and put it into the cache)\n/******/ \t\tvar module = installedModules[moduleId] = {\n/******/ \t\t\texports: {},\n/******/ \t\t\tid: moduleId,\n/******/ \t\t\tloaded: false\n/******/ \t\t};\n/******/\n/******/ \t\t// Execute the module function\n/******/ \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n/******/\n/******/ \t\t// Flag the module as loaded\n/******/ \t\tmodule.loaded = true;\n/******/\n/******/ \t\t// Return the exports of the module\n/******/ \t\treturn module.exports;\n/******/ \t}\n/******/\n/******/ \t// This file contains only the entry chunk.\n/******/ \t// The chunk loading function for additional chunks\n/******/ \t__webpack_require__.e = function requireEnsure(chunkId, callback) {\n/******/ \t\t// \"0\" is the signal for \"already loaded\"\n/******/ \t\tif(installedChunks[chunkId] === 0)\n/******/ \t\t\treturn callback.call(null, __webpack_require__);\n/******/\n/******/ \t\t// an array means \"currently loading\".\n/******/ \t\tif(installedChunks[chunkId] !== undefined) {\n/******/ \t\t\tinstalledChunks[chunkId].push(callback);\n/******/ \t\t} else {\n/******/ \t\t\t// start chunk loading\n/******/ \t\t\tinstalledChunks[chunkId] = [callback];\n/******/ \t\t\tvar head = document.getElementsByTagName('head')[0];\n/******/ \t\t\tvar script = document.createElement('script');\n/******/ \t\t\tscript.type = 'text/javascript';\n/******/ \t\t\tscript.charset = 'utf-8';\n/******/ \t\t\tscript.async = true;\n/******/\n/******/ \t\t\tscript.src = __webpack_require__.p + \"chunks/\" + chunkId + \".\" + {\"0\":\"285786eca84d7b7471ac\",\"1\":\"765e28a442160395e71a\",\"2\":\"f6372652ce6895fe1fad\",\"3\":\"31f4515503a814e0e3a3\",\"4\":\"ef9b48820b09477986b5\",\"5\":\"3aa55302c802f48db900\",\"6\":\"0db5d3ff22944de7edd5\",\"7\":\"b2e2b3f88a04f6b296dd\",\"8\":\"292c6a157d71f0b21b6f\",\"9\":\"8ad948dd393d38f07a7a\",\"10\":\"c1c9496ebfc8949f3de2\",\"11\":\"18f1cc16362b76373c40\",\"12\":\"f6b6e770dfe4a5949444\",\"13\":\"066b4d36814efa6c485f\",\"14\":\"72a6fc22c9b0cc8bcd14\",\"15\":\"7de380008f4ec6ea4b5b\",\"16\":\"cddb601c07a2df6564b2\",\"17\":\"26a29ec18e773c2be9b2\",\"18\":\"b7098bfe5401f75973fa\",\"19\":\"2061dca8438f415029a3\",\"20\":\"17e9e766625991aa0a7b\",\"21\":\"2b48c7e5fe59775d5eb3\",\"22\":\"2a919b7f6014457264e8\",\"23\":\"18bb4231e440e17f29a2\",\"24\":\"bb29ce250a1b77491420\",\"25\":\"6cb865e768a122d52b4d\",\"26\":\"2bd61452982a0d4de0a3\",\"27\":\"1872ae081edb64216589\",\"28\":\"1e240391cc0358805408\",\"29\":\"556152a9334761b0d972\",\"30\":\"a2eb870c6d29644ed9b5\",\"32\":\"92a026dcad120ccdf15f\",\"33\":\"6636c1fd8aec6fc072b8\"}[chunkId] + \".js\";\n/******/ \t\t\thead.appendChild(script);\n/******/ \t\t}\n/******/ \t};\n/******/\n/******/ \t// expose the modules object (__webpack_modules__)\n/******/ \t__webpack_require__.m = modules;\n/******/\n/******/ \t// expose the module cache\n/******/ \t__webpack_require__.c = installedModules;\n/******/\n/******/ \t// __webpack_public_path__\n/******/ \t__webpack_require__.p = \"/static/\";\n/******/ })\n/************************************************************************/\n/******/ ((function(modules) {\n\t// Check all modules for deduplicated modules\n\tfor(var i in modules) {\n\t\tif(Object.prototype.hasOwnProperty.call(modules, i)) {\n\t\t\tswitch(typeof modules[i]) {\n\t\t\tcase \"function\": break;\n\t\t\tcase \"object\":\n\t\t\t\t// Module can be created from a template\n\t\t\t\tmodules[i] = (function(_m) {\n\t\t\t\t\tvar args = _m.slice(1), fn = modules[_m[0]];\n\t\t\t\t\treturn function (a,b,c) {\n\t\t\t\t\t\tfn.apply(this, [a,b,c].concat(args));\n\t\t\t\t\t};\n\t\t\t\t}(modules[i]));\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\t// Module is a copy of another module\n\t\t\t\tmodules[i] = modules[modules[i]];\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\treturn modules;\n}([])));\n\n\n/** WEBPACK FOOTER **\n ** common.js\n **/"," \t// install a JSONP callback for chunk loading\n \tvar parentJsonpFunction = window[\"webpackJsonp\"];\n \twindow[\"webpackJsonp\"] = function webpackJsonpCallback(chunkIds, moreModules) {\n \t\t// add \"moreModules\" to the modules object,\n \t\t// then flag all \"chunkIds\" as loaded and fire callback\n \t\tvar moduleId, chunkId, i = 0, callbacks = [];\n \t\tfor(;i < chunkIds.length; i++) {\n \t\t\tchunkId = chunkIds[i];\n \t\t\tif(installedChunks[chunkId])\n \t\t\t\tcallbacks.push.apply(callbacks, installedChunks[chunkId]);\n \t\t\tinstalledChunks[chunkId] = 0;\n \t\t}\n \t\tfor(moduleId in moreModules) {\n \t\t\tvar _m = moreModules[moduleId];\n\n \t\t\t// Check if module is deduplicated\n \t\t\tswitch(typeof _m) {\n \t\t\tcase \"object\":\n \t\t\t\t// Module can be created from a template\n \t\t\t\tmodules[moduleId] = (function(_m) {\n \t\t\t\t\tvar args = _m.slice(1), templateId = _m[0];\n \t\t\t\t\treturn function (a,b,c) {\n \t\t\t\t\t\tmodules[templateId].apply(this, [a,b,c].concat(args));\n \t\t\t\t\t};\n \t\t\t\t}(_m));\n \t\t\t\tbreak;\n \t\t\tcase \"function\":\n \t\t\t\t// Normal module\n \t\t\t\tmodules[moduleId] = _m;\n \t\t\t\tbreak;\n \t\t\tdefault:\n \t\t\t\t// Module is a copy of another module\n \t\t\t\tmodules[moduleId] = modules[_m];\n \t\t\t\tbreak;\n \t\t\t}\n \t\t}\n \t\tif(parentJsonpFunction) parentJsonpFunction(chunkIds, moreModules);\n \t\twhile(callbacks.length)\n \t\t\tcallbacks.shift().call(null, __webpack_require__);\n \t\tif(moreModules[0]) {\n \t\t\tinstalledModules[0] = 0;\n \t\t\treturn __webpack_require__(0);\n \t\t}\n \t};\n\n \t// The module cache\n \tvar installedModules = {};\n\n \t// object to store loaded and loading chunks\n \t// \"0\" means \"already loaded\"\n \t// Array means \"loading\", array contains callbacks\n \tvar installedChunks = {\n \t\t31:0\n \t};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId])\n \t\t\treturn installedModules[moduleId].exports;\n\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\texports: {},\n \t\t\tid: moduleId,\n \t\t\tloaded: false\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.loaded = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n \t// This file contains only the entry chunk.\n \t// The chunk loading function for additional chunks\n \t__webpack_require__.e = function requireEnsure(chunkId, callback) {\n \t\t// \"0\" is the signal for \"already loaded\"\n \t\tif(installedChunks[chunkId] === 0)\n \t\t\treturn callback.call(null, __webpack_require__);\n\n \t\t// an array means \"currently loading\".\n \t\tif(installedChunks[chunkId] !== undefined) {\n \t\t\tinstalledChunks[chunkId].push(callback);\n \t\t} else {\n \t\t\t// start chunk loading\n \t\t\tinstalledChunks[chunkId] = [callback];\n \t\t\tvar head = document.getElementsByTagName('head')[0];\n \t\t\tvar script = document.createElement('script');\n \t\t\tscript.type = 'text/javascript';\n \t\t\tscript.charset = 'utf-8';\n \t\t\tscript.async = true;\n\n \t\t\tscript.src = __webpack_require__.p + \"chunks/\" + chunkId + \".\" + {\"0\":\"285786eca84d7b7471ac\",\"1\":\"765e28a442160395e71a\",\"2\":\"f6372652ce6895fe1fad\",\"3\":\"31f4515503a814e0e3a3\",\"4\":\"ef9b48820b09477986b5\",\"5\":\"3aa55302c802f48db900\",\"6\":\"0db5d3ff22944de7edd5\",\"7\":\"b2e2b3f88a04f6b296dd\",\"8\":\"292c6a157d71f0b21b6f\",\"9\":\"8ad948dd393d38f07a7a\",\"10\":\"c1c9496ebfc8949f3de2\",\"11\":\"18f1cc16362b76373c40\",\"12\":\"f6b6e770dfe4a5949444\",\"13\":\"066b4d36814efa6c485f\",\"14\":\"72a6fc22c9b0cc8bcd14\",\"15\":\"7de380008f4ec6ea4b5b\",\"16\":\"cddb601c07a2df6564b2\",\"17\":\"26a29ec18e773c2be9b2\",\"18\":\"b7098bfe5401f75973fa\",\"19\":\"2061dca8438f415029a3\",\"20\":\"17e9e766625991aa0a7b\",\"21\":\"2b48c7e5fe59775d5eb3\",\"22\":\"2a919b7f6014457264e8\",\"23\":\"18bb4231e440e17f29a2\",\"24\":\"bb29ce250a1b77491420\",\"25\":\"6cb865e768a122d52b4d\",\"26\":\"2bd61452982a0d4de0a3\",\"27\":\"1872ae081edb64216589\",\"28\":\"1e240391cc0358805408\",\"29\":\"556152a9334761b0d972\",\"30\":\"a2eb870c6d29644ed9b5\",\"32\":\"92a026dcad120ccdf15f\",\"33\":\"6636c1fd8aec6fc072b8\"}[chunkId] + \".js\";\n \t\t\thead.appendChild(script);\n \t\t}\n \t};\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"/static/\";\n\n\n\n/** WEBPACK FOOTER **\n ** webpack/bootstrap aae90f74dc2315755797\n **/"],"sourceRoot":""}
|
|
1
|
+
{"version":3,"sources":["webpack:///common.js","webpack:///webpack/bootstrap 47bc9a5c7fac4343d504"],"names":["modules","__webpack_require__","moduleId","installedModules","exports","module","id","loaded","call","parentJsonpFunction","window","chunkIds","moreModules","chunkId","i","callbacks","length","installedChunks","push","apply","_m","args","slice","templateId","a","b","c","this","concat","shift","31","e","callback","undefined","head","document","getElementsByTagName","script","createElement","type","charset","async","src","p","0","1","2","3","4","5","6","7","8","9","10","11","12","13","14","15","16","17","18","19","20","21","22","23","24","25","26","27","28","29","30","32","33","appendChild","m","Object","prototype","hasOwnProperty","fn"],"mappings":"CAAS,SAAUA,GCwDnB,QAAAC,qBAAAC,GAGA,GAAAC,EAAAD,GACA,MAAAC,GAAAD,GAAAE,OAGA,IAAAC,GAAAF,EAAAD,IACAE,WACAE,GAAAJ,EACAK,QAAA,EAUA,OANAP,GAAAE,GAAAM,KAAAH,EAAAD,QAAAC,IAAAD,QAAAH,qBAGAI,EAAAE,QAAA,EAGAF,EAAAD,QA3EA,GAAAK,GAAAC,OAAA,YACAA,QAAA,sBAAAC,EAAAC,GAIA,IADA,GAAAV,GAAAW,EAAAC,EAAA,EAAAC,KACQD,EAAAH,EAAAK,OAAoBF,IAC5BD,EAAAF,EAAAG,GACAG,EAAAJ,IACAE,EAAAG,KAAAC,MAAAJ,EAAAE,EAAAJ,IACAI,EAAAJ,GAAA,CAEA,KAAAX,IAAAU,GAAA,CACA,GAAAQ,GAAAR,EAAAV,EAGA,cAAAkB,IACA,aAEApB,EAAAE,GAAA,SAAAkB,GACA,GAAAC,GAAAD,EAAAE,MAAA,GAAAC,EAAAH,EAAA,EACA,iBAAAI,EAAAC,EAAAC,GACA1B,EAAAuB,GAAAJ,MAAAQ,MAAAH,EAAAC,EAAAC,GAAAE,OAAAP,MAEMD,EACN,MACA,gBAEApB,EAAAE,GAAAkB,CACA,MACA,SAEApB,EAAAE,GAAAF,EAAAoB,IAKA,IADAX,KAAAE,EAAAC,GACAG,EAAAC,QACAD,EAAAc,QAAArB,KAAA,KAAAP,oBACA,IAAAW,EAAA,GAEA,MADAT,GAAA,KACAF,oBAAA,GAKA,IAAAE,MAKAc,GACAa,GAAA,EA6BA7B,qBAAA8B,EAAA,SAAAlB,EAAAmB,GAEA,OAAAf,EAAAJ,GACA,MAAAmB,GAAAxB,KAAA,KAAAP,oBAGA,IAAAgC,SAAAhB,EAAAJ,GACAI,EAAAJ,GAAAK,KAAAc,OACI,CAEJf,EAAAJ,IAAAmB,EACA,IAAAE,GAAAC,SAAAC,qBAAA,WACAC,EAAAF,SAAAG,cAAA,SACAD,GAAAE,KAAA,kBACAF,EAAAG,QAAA,QACAH,EAAAI,OAAA,EAEAJ,EAAAK,IAAAzC,oBAAA0C,EAAA,UAAA9B,EAAA,KAAsE+B,EAAA,uBAAAC,EAAA,uBAAAC,EAAA,uBAAAC,EAAA,uBAAAC,EAAA,uBAAAC,EAAA,uBAAAC,EAAA,uBAAAC,EAAA,uBAAAC,EAAA,uBAAAC,EAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,uBAAAC,GAAA,wBAAk5B/D,GAAA,MACx9BqB,EAAA2C,YAAAxC,KAKApC,oBAAA6E,EAAA9E,EAGAC,oBAAAyB,EAAAvB,EAGAF,oBAAA0C,EAAA,YDIW,SAAS3C,GAEnB,IAAI,GAAIc,KAAKd,GACZ,GAAG+E,OAAOC,UAAUC,eAAezE,KAAKR,EAASc,GAChD,aAAcd,GAAQc,IACtB,IAAK,WAAY,KACjB,KAAK,SAEJd,EAAQc,GAAM,SAASM,GACtB,GAAIC,GAAOD,EAAGE,MAAM,GAAI4D,EAAKlF,EAAQoB,EAAG,GACxC,OAAO,UAAUI,EAAEC,EAAEC,GACpBwD,EAAG/D,MAAMQ,MAAOH,EAAEC,EAAEC,GAAGE,OAAOP,MAE9BrB,EAAQc,GACV,MACD,SAECd,EAAQc,GAAKd,EAAQA,EAAQc,IAKhC,MAAOd","file":"common.js","sourcesContent":["/******/ (function(modules) { // webpackBootstrap\n/******/ \t// install a JSONP callback for chunk loading\n/******/ \tvar parentJsonpFunction = window[\"webpackJsonp\"];\n/******/ \twindow[\"webpackJsonp\"] = function webpackJsonpCallback(chunkIds, moreModules) {\n/******/ \t\t// add \"moreModules\" to the modules object,\n/******/ \t\t// then flag all \"chunkIds\" as loaded and fire callback\n/******/ \t\tvar moduleId, chunkId, i = 0, callbacks = [];\n/******/ \t\tfor(;i < chunkIds.length; i++) {\n/******/ \t\t\tchunkId = chunkIds[i];\n/******/ \t\t\tif(installedChunks[chunkId])\n/******/ \t\t\t\tcallbacks.push.apply(callbacks, installedChunks[chunkId]);\n/******/ \t\t\tinstalledChunks[chunkId] = 0;\n/******/ \t\t}\n/******/ \t\tfor(moduleId in moreModules) {\n/******/ \t\t\tvar _m = moreModules[moduleId];\n/******/\n/******/ \t\t\t// Check if module is deduplicated\n/******/ \t\t\tswitch(typeof _m) {\n/******/ \t\t\tcase \"object\":\n/******/ \t\t\t\t// Module can be created from a template\n/******/ \t\t\t\tmodules[moduleId] = (function(_m) {\n/******/ \t\t\t\t\tvar args = _m.slice(1), templateId = _m[0];\n/******/ \t\t\t\t\treturn function (a,b,c) {\n/******/ \t\t\t\t\t\tmodules[templateId].apply(this, [a,b,c].concat(args));\n/******/ \t\t\t\t\t};\n/******/ \t\t\t\t}(_m));\n/******/ \t\t\t\tbreak;\n/******/ \t\t\tcase \"function\":\n/******/ \t\t\t\t// Normal module\n/******/ \t\t\t\tmodules[moduleId] = _m;\n/******/ \t\t\t\tbreak;\n/******/ \t\t\tdefault:\n/******/ \t\t\t\t// Module is a copy of another module\n/******/ \t\t\t\tmodules[moduleId] = modules[_m];\n/******/ \t\t\t\tbreak;\n/******/ \t\t\t}\n/******/ \t\t}\n/******/ \t\tif(parentJsonpFunction) parentJsonpFunction(chunkIds, moreModules);\n/******/ \t\twhile(callbacks.length)\n/******/ \t\t\tcallbacks.shift().call(null, __webpack_require__);\n/******/ \t\tif(moreModules[0]) {\n/******/ \t\t\tinstalledModules[0] = 0;\n/******/ \t\t\treturn __webpack_require__(0);\n/******/ \t\t}\n/******/ \t};\n/******/\n/******/ \t// The module cache\n/******/ \tvar installedModules = {};\n/******/\n/******/ \t// object to store loaded and loading chunks\n/******/ \t// \"0\" means \"already loaded\"\n/******/ \t// Array means \"loading\", array contains callbacks\n/******/ \tvar installedChunks = {\n/******/ \t\t31:0\n/******/ \t};\n/******/\n/******/ \t// The require function\n/******/ \tfunction __webpack_require__(moduleId) {\n/******/\n/******/ \t\t// Check if module is in cache\n/******/ \t\tif(installedModules[moduleId])\n/******/ \t\t\treturn installedModules[moduleId].exports;\n/******/\n/******/ \t\t// Create a new module (and put it into the cache)\n/******/ \t\tvar module = installedModules[moduleId] = {\n/******/ \t\t\texports: {},\n/******/ \t\t\tid: moduleId,\n/******/ \t\t\tloaded: false\n/******/ \t\t};\n/******/\n/******/ \t\t// Execute the module function\n/******/ \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n/******/\n/******/ \t\t// Flag the module as loaded\n/******/ \t\tmodule.loaded = true;\n/******/\n/******/ \t\t// Return the exports of the module\n/******/ \t\treturn module.exports;\n/******/ \t}\n/******/\n/******/ \t// This file contains only the entry chunk.\n/******/ \t// The chunk loading function for additional chunks\n/******/ \t__webpack_require__.e = function requireEnsure(chunkId, callback) {\n/******/ \t\t// \"0\" is the signal for \"already loaded\"\n/******/ \t\tif(installedChunks[chunkId] === 0)\n/******/ \t\t\treturn callback.call(null, __webpack_require__);\n/******/\n/******/ \t\t// an array means \"currently loading\".\n/******/ \t\tif(installedChunks[chunkId] !== undefined) {\n/******/ \t\t\tinstalledChunks[chunkId].push(callback);\n/******/ \t\t} else {\n/******/ \t\t\t// start chunk loading\n/******/ \t\t\tinstalledChunks[chunkId] = [callback];\n/******/ \t\t\tvar head = document.getElementsByTagName('head')[0];\n/******/ \t\t\tvar script = document.createElement('script');\n/******/ \t\t\tscript.type = 'text/javascript';\n/******/ \t\t\tscript.charset = 'utf-8';\n/******/ \t\t\tscript.async = true;\n/******/\n/******/ \t\t\tscript.src = __webpack_require__.p + \"chunks/\" + chunkId + \".\" + {\"0\":\"285786eca84d7b7471ac\",\"1\":\"765e28a442160395e71a\",\"2\":\"f6372652ce6895fe1fad\",\"3\":\"31f4515503a814e0e3a3\",\"4\":\"ef9b48820b09477986b5\",\"5\":\"9049c2001a2f21930f78\",\"6\":\"ad092769b0983a6eec2a\",\"7\":\"b2e2b3f88a04f6b296dd\",\"8\":\"292c6a157d71f0b21b6f\",\"9\":\"8ad948dd393d38f07a7a\",\"10\":\"c1c9496ebfc8949f3de2\",\"11\":\"18f1cc16362b76373c40\",\"12\":\"f6b6e770dfe4a5949444\",\"13\":\"066b4d36814efa6c485f\",\"14\":\"72a6fc22c9b0cc8bcd14\",\"15\":\"7de380008f4ec6ea4b5b\",\"16\":\"cddb601c07a2df6564b2\",\"17\":\"26a29ec18e773c2be9b2\",\"18\":\"b7098bfe5401f75973fa\",\"19\":\"2061dca8438f415029a3\",\"20\":\"17e9e766625991aa0a7b\",\"21\":\"2b48c7e5fe59775d5eb3\",\"22\":\"2a919b7f6014457264e8\",\"23\":\"18bb4231e440e17f29a2\",\"24\":\"bb29ce250a1b77491420\",\"25\":\"6cb865e768a122d52b4d\",\"26\":\"2bd61452982a0d4de0a3\",\"27\":\"1872ae081edb64216589\",\"28\":\"1e240391cc0358805408\",\"29\":\"556152a9334761b0d972\",\"30\":\"a2eb870c6d29644ed9b5\",\"32\":\"92a026dcad120ccdf15f\",\"33\":\"6636c1fd8aec6fc072b8\"}[chunkId] + \".js\";\n/******/ \t\t\thead.appendChild(script);\n/******/ \t\t}\n/******/ \t};\n/******/\n/******/ \t// expose the modules object (__webpack_modules__)\n/******/ \t__webpack_require__.m = modules;\n/******/\n/******/ \t// expose the module cache\n/******/ \t__webpack_require__.c = installedModules;\n/******/\n/******/ \t// __webpack_public_path__\n/******/ \t__webpack_require__.p = \"/static/\";\n/******/ })\n/************************************************************************/\n/******/ ((function(modules) {\n\t// Check all modules for deduplicated modules\n\tfor(var i in modules) {\n\t\tif(Object.prototype.hasOwnProperty.call(modules, i)) {\n\t\t\tswitch(typeof modules[i]) {\n\t\t\tcase \"function\": break;\n\t\t\tcase \"object\":\n\t\t\t\t// Module can be created from a template\n\t\t\t\tmodules[i] = (function(_m) {\n\t\t\t\t\tvar args = _m.slice(1), fn = modules[_m[0]];\n\t\t\t\t\treturn function (a,b,c) {\n\t\t\t\t\t\tfn.apply(this, [a,b,c].concat(args));\n\t\t\t\t\t};\n\t\t\t\t}(modules[i]));\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\t// Module is a copy of another module\n\t\t\t\tmodules[i] = modules[modules[i]];\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\treturn modules;\n}([])));\n\n\n/** WEBPACK FOOTER **\n ** common.js\n **/"," \t// install a JSONP callback for chunk loading\n \tvar parentJsonpFunction = window[\"webpackJsonp\"];\n \twindow[\"webpackJsonp\"] = function webpackJsonpCallback(chunkIds, moreModules) {\n \t\t// add \"moreModules\" to the modules object,\n \t\t// then flag all \"chunkIds\" as loaded and fire callback\n \t\tvar moduleId, chunkId, i = 0, callbacks = [];\n \t\tfor(;i < chunkIds.length; i++) {\n \t\t\tchunkId = chunkIds[i];\n \t\t\tif(installedChunks[chunkId])\n \t\t\t\tcallbacks.push.apply(callbacks, installedChunks[chunkId]);\n \t\t\tinstalledChunks[chunkId] = 0;\n \t\t}\n \t\tfor(moduleId in moreModules) {\n \t\t\tvar _m = moreModules[moduleId];\n\n \t\t\t// Check if module is deduplicated\n \t\t\tswitch(typeof _m) {\n \t\t\tcase \"object\":\n \t\t\t\t// Module can be created from a template\n \t\t\t\tmodules[moduleId] = (function(_m) {\n \t\t\t\t\tvar args = _m.slice(1), templateId = _m[0];\n \t\t\t\t\treturn function (a,b,c) {\n \t\t\t\t\t\tmodules[templateId].apply(this, [a,b,c].concat(args));\n \t\t\t\t\t};\n \t\t\t\t}(_m));\n \t\t\t\tbreak;\n \t\t\tcase \"function\":\n \t\t\t\t// Normal module\n \t\t\t\tmodules[moduleId] = _m;\n \t\t\t\tbreak;\n \t\t\tdefault:\n \t\t\t\t// Module is a copy of another module\n \t\t\t\tmodules[moduleId] = modules[_m];\n \t\t\t\tbreak;\n \t\t\t}\n \t\t}\n \t\tif(parentJsonpFunction) parentJsonpFunction(chunkIds, moreModules);\n \t\twhile(callbacks.length)\n \t\t\tcallbacks.shift().call(null, __webpack_require__);\n \t\tif(moreModules[0]) {\n \t\t\tinstalledModules[0] = 0;\n \t\t\treturn __webpack_require__(0);\n \t\t}\n \t};\n\n \t// The module cache\n \tvar installedModules = {};\n\n \t// object to store loaded and loading chunks\n \t// \"0\" means \"already loaded\"\n \t// Array means \"loading\", array contains callbacks\n \tvar installedChunks = {\n \t\t31:0\n \t};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId])\n \t\t\treturn installedModules[moduleId].exports;\n\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\texports: {},\n \t\t\tid: moduleId,\n \t\t\tloaded: false\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.loaded = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n \t// This file contains only the entry chunk.\n \t// The chunk loading function for additional chunks\n \t__webpack_require__.e = function requireEnsure(chunkId, callback) {\n \t\t// \"0\" is the signal for \"already loaded\"\n \t\tif(installedChunks[chunkId] === 0)\n \t\t\treturn callback.call(null, __webpack_require__);\n\n \t\t// an array means \"currently loading\".\n \t\tif(installedChunks[chunkId] !== undefined) {\n \t\t\tinstalledChunks[chunkId].push(callback);\n \t\t} else {\n \t\t\t// start chunk loading\n \t\t\tinstalledChunks[chunkId] = [callback];\n \t\t\tvar head = document.getElementsByTagName('head')[0];\n \t\t\tvar script = document.createElement('script');\n \t\t\tscript.type = 'text/javascript';\n \t\t\tscript.charset = 'utf-8';\n \t\t\tscript.async = true;\n\n \t\t\tscript.src = __webpack_require__.p + \"chunks/\" + chunkId + \".\" + {\"0\":\"285786eca84d7b7471ac\",\"1\":\"765e28a442160395e71a\",\"2\":\"f6372652ce6895fe1fad\",\"3\":\"31f4515503a814e0e3a3\",\"4\":\"ef9b48820b09477986b5\",\"5\":\"9049c2001a2f21930f78\",\"6\":\"ad092769b0983a6eec2a\",\"7\":\"b2e2b3f88a04f6b296dd\",\"8\":\"292c6a157d71f0b21b6f\",\"9\":\"8ad948dd393d38f07a7a\",\"10\":\"c1c9496ebfc8949f3de2\",\"11\":\"18f1cc16362b76373c40\",\"12\":\"f6b6e770dfe4a5949444\",\"13\":\"066b4d36814efa6c485f\",\"14\":\"72a6fc22c9b0cc8bcd14\",\"15\":\"7de380008f4ec6ea4b5b\",\"16\":\"cddb601c07a2df6564b2\",\"17\":\"26a29ec18e773c2be9b2\",\"18\":\"b7098bfe5401f75973fa\",\"19\":\"2061dca8438f415029a3\",\"20\":\"17e9e766625991aa0a7b\",\"21\":\"2b48c7e5fe59775d5eb3\",\"22\":\"2a919b7f6014457264e8\",\"23\":\"18bb4231e440e17f29a2\",\"24\":\"bb29ce250a1b77491420\",\"25\":\"6cb865e768a122d52b4d\",\"26\":\"2bd61452982a0d4de0a3\",\"27\":\"1872ae081edb64216589\",\"28\":\"1e240391cc0358805408\",\"29\":\"556152a9334761b0d972\",\"30\":\"a2eb870c6d29644ed9b5\",\"32\":\"92a026dcad120ccdf15f\",\"33\":\"6636c1fd8aec6fc072b8\"}[chunkId] + \".js\";\n \t\t\thead.appendChild(script);\n \t\t}\n \t};\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"/static/\";\n\n\n\n/** WEBPACK FOOTER **\n ** webpack/bootstrap 47bc9a5c7fac4343d504\n **/"],"sourceRoot":""}
|
udata/tests/api/test_auth_api.py
CHANGED
|
@@ -59,6 +59,7 @@ def oauth(app, request):
|
|
|
59
59
|
name="test-client",
|
|
60
60
|
owner=UserFactory(),
|
|
61
61
|
redirect_uris=["https://test.org/callback"],
|
|
62
|
+
secret="suchs3cr3t",
|
|
62
63
|
)
|
|
63
64
|
kwargs.update(custom_kwargs)
|
|
64
65
|
return OAuth2Client.objects.create(**kwargs)
|
|
@@ -456,6 +457,93 @@ class APIAuthTest:
|
|
|
456
457
|
assert response.content_type == "application/json"
|
|
457
458
|
assert response.json == {"success": True}
|
|
458
459
|
|
|
460
|
+
@pytest.mark.oauth(secret=None)
|
|
461
|
+
def test_s256_code_challenge_success_no_client_secret(self, client, oauth):
|
|
462
|
+
"""Authenticate through an OAuth client that has no secret associated (public client)"""
|
|
463
|
+
code_verifier = generate_token(48)
|
|
464
|
+
code_challenge = create_s256_code_challenge(code_verifier)
|
|
465
|
+
|
|
466
|
+
client.login()
|
|
467
|
+
|
|
468
|
+
response = client.post(
|
|
469
|
+
url_for(
|
|
470
|
+
"oauth.authorize",
|
|
471
|
+
response_type="code",
|
|
472
|
+
client_id=oauth.client_id,
|
|
473
|
+
code_challenge=code_challenge,
|
|
474
|
+
code_challenge_method="S256",
|
|
475
|
+
),
|
|
476
|
+
{
|
|
477
|
+
"scope": "default",
|
|
478
|
+
"accept": "",
|
|
479
|
+
},
|
|
480
|
+
)
|
|
481
|
+
assert "code=" in response.location
|
|
482
|
+
|
|
483
|
+
params = dict(url_decode(urlparse.urlparse(response.location).query))
|
|
484
|
+
code = params["code"]
|
|
485
|
+
|
|
486
|
+
response = client.post(
|
|
487
|
+
url_for("oauth.token"),
|
|
488
|
+
{
|
|
489
|
+
"grant_type": "authorization_code",
|
|
490
|
+
"code": code,
|
|
491
|
+
"code_verifier": code_verifier,
|
|
492
|
+
"client_id": oauth.client_id,
|
|
493
|
+
},
|
|
494
|
+
)
|
|
495
|
+
|
|
496
|
+
assert200(response)
|
|
497
|
+
assert response.content_type == "application/json"
|
|
498
|
+
assert "access_token" in response.json
|
|
499
|
+
|
|
500
|
+
token = response.json["access_token"]
|
|
501
|
+
|
|
502
|
+
response = client.post(
|
|
503
|
+
url_for("api.fake"), headers={"Authorization": " ".join(["Bearer", token])}
|
|
504
|
+
)
|
|
505
|
+
|
|
506
|
+
assert200(response)
|
|
507
|
+
assert response.content_type == "application/json"
|
|
508
|
+
assert response.json == {"success": True}
|
|
509
|
+
|
|
510
|
+
def test_s256_code_challenge_missing_client_secret(self, client, oauth):
|
|
511
|
+
"""Fail authentication through an OAuth client with missing secret"""
|
|
512
|
+
code_verifier = generate_token(48)
|
|
513
|
+
code_challenge = create_s256_code_challenge(code_verifier)
|
|
514
|
+
|
|
515
|
+
client.login()
|
|
516
|
+
|
|
517
|
+
response = client.post(
|
|
518
|
+
url_for(
|
|
519
|
+
"oauth.authorize",
|
|
520
|
+
response_type="code",
|
|
521
|
+
client_id=oauth.client_id,
|
|
522
|
+
code_challenge=code_challenge,
|
|
523
|
+
code_challenge_method="S256",
|
|
524
|
+
),
|
|
525
|
+
{
|
|
526
|
+
"scope": "default",
|
|
527
|
+
"accept": "",
|
|
528
|
+
},
|
|
529
|
+
)
|
|
530
|
+
assert "code=" in response.location
|
|
531
|
+
|
|
532
|
+
params = dict(url_decode(urlparse.urlparse(response.location).query))
|
|
533
|
+
code = params["code"]
|
|
534
|
+
|
|
535
|
+
response = client.post(
|
|
536
|
+
url_for("oauth.token"),
|
|
537
|
+
{
|
|
538
|
+
"grant_type": "authorization_code",
|
|
539
|
+
"code": code,
|
|
540
|
+
"code_verifier": code_verifier,
|
|
541
|
+
"client_id": oauth.client_id,
|
|
542
|
+
},
|
|
543
|
+
)
|
|
544
|
+
|
|
545
|
+
assert401(response)
|
|
546
|
+
|
|
459
547
|
def test_authorization_multiple_grant_token(self, client, oauth):
|
|
460
548
|
for i in range(3):
|
|
461
549
|
client.login()
|
|
@@ -2,6 +2,7 @@ from datetime import datetime
|
|
|
2
2
|
|
|
3
3
|
import pytest
|
|
4
4
|
from flask import url_for
|
|
5
|
+
from werkzeug.test import TestResponse
|
|
5
6
|
|
|
6
7
|
from udata.core.badges.factories import badge_factory
|
|
7
8
|
from udata.core.dataset.factories import DatasetFactory
|
|
@@ -25,6 +26,11 @@ pytestmark = [
|
|
|
25
26
|
]
|
|
26
27
|
|
|
27
28
|
|
|
29
|
+
def reuse_in_response(response: TestResponse, reuse: Reuse) -> bool:
|
|
30
|
+
only_reuse = [r for r in response.json["data"] if r["id"] == str(reuse.id)]
|
|
31
|
+
return len(only_reuse) > 0
|
|
32
|
+
|
|
33
|
+
|
|
28
34
|
class ReuseAPITest:
|
|
29
35
|
modules = []
|
|
30
36
|
|
|
@@ -88,6 +94,7 @@ class ReuseAPITest:
|
|
|
88
94
|
# Keep only featured reuses (if any)
|
|
89
95
|
data = [reuse for reuse in response.json["data"] if reuse["featured"]]
|
|
90
96
|
assert len(data) == 0 # It did not return any featured reuse
|
|
97
|
+
|
|
91
98
|
# filter on topic
|
|
92
99
|
response = api.get(url_for("api.reuses", topic=topic_reuse.topic))
|
|
93
100
|
assert200(response)
|
|
@@ -118,6 +125,104 @@ class ReuseAPITest:
|
|
|
118
125
|
response = api.get(url_for("api.reuses", organization="org-id"))
|
|
119
126
|
assert400(response)
|
|
120
127
|
|
|
128
|
+
def test_reuse_api_list_filter_private(self, api) -> None:
|
|
129
|
+
"""Should filters reuses results based on the `private` filter"""
|
|
130
|
+
user = UserFactory()
|
|
131
|
+
public_reuse: Reuse = ReuseFactory()
|
|
132
|
+
private_reuse: Reuse = ReuseFactory(private=True, owner=user)
|
|
133
|
+
|
|
134
|
+
# Only public reuses for non-authenticated user.
|
|
135
|
+
response: TestResponse = api.get(url_for("api.reuses"))
|
|
136
|
+
assert200(response)
|
|
137
|
+
assert len(response.json["data"]) == 1
|
|
138
|
+
assert reuse_in_response(response, public_reuse)
|
|
139
|
+
|
|
140
|
+
# With an authenticated user.
|
|
141
|
+
api.login(user)
|
|
142
|
+
# all the reuses (by default)
|
|
143
|
+
response = api.get(url_for("api.reuses"))
|
|
144
|
+
assert200(response)
|
|
145
|
+
assert len(response.json["data"]) == 2 # Return everything
|
|
146
|
+
assert reuse_in_response(response, public_reuse)
|
|
147
|
+
assert reuse_in_response(response, private_reuse)
|
|
148
|
+
|
|
149
|
+
# only public
|
|
150
|
+
response = api.get(url_for("api.reuses", private="false"))
|
|
151
|
+
assert200(response)
|
|
152
|
+
assert len(response.json["data"]) == 1 # Don't return the private reuse
|
|
153
|
+
assert reuse_in_response(response, public_reuse)
|
|
154
|
+
|
|
155
|
+
# only private
|
|
156
|
+
response = api.get(url_for("api.reuses", private="true"))
|
|
157
|
+
assert200(response)
|
|
158
|
+
assert len(response.json["data"]) == 1 # Return only the private
|
|
159
|
+
assert reuse_in_response(response, private_reuse)
|
|
160
|
+
|
|
161
|
+
def test_reuse_api_list_filter_private_only_owned_by_user(self, api) -> None:
|
|
162
|
+
"""Should only return private reuses that are owned."""
|
|
163
|
+
user = UserFactory()
|
|
164
|
+
member = Member(user=user, role="editor")
|
|
165
|
+
org = OrganizationFactory(members=[member])
|
|
166
|
+
private_owned: Reuse = ReuseFactory(private=True, owner=user)
|
|
167
|
+
private_owned_through_org: Reuse = ReuseFactory(private=True, organization=org)
|
|
168
|
+
private_not_owned: Reuse = ReuseFactory(private=True)
|
|
169
|
+
|
|
170
|
+
# Only public reuses for non-authenticated user.
|
|
171
|
+
response: TestResponse = api.get(url_for("api.reuses"))
|
|
172
|
+
assert200(response)
|
|
173
|
+
assert len(response.json["data"]) == 0
|
|
174
|
+
|
|
175
|
+
# With an authenticated user.
|
|
176
|
+
api.login(user)
|
|
177
|
+
response = api.get(url_for("api.reuses"))
|
|
178
|
+
assert200(response)
|
|
179
|
+
assert len(response.json["data"]) == 2 # Only the owned reuses
|
|
180
|
+
assert reuse_in_response(response, private_owned)
|
|
181
|
+
assert reuse_in_response(response, private_owned_through_org)
|
|
182
|
+
assert not reuse_in_response(response, private_not_owned)
|
|
183
|
+
|
|
184
|
+
# Still no private returned if `private=False`
|
|
185
|
+
response = api.get(url_for("api.reuses", private=False))
|
|
186
|
+
assert200(response)
|
|
187
|
+
assert len(response.json["data"]) == 0
|
|
188
|
+
|
|
189
|
+
# Still only return owned private reuses
|
|
190
|
+
response = api.get(url_for("api.reuses", private=True))
|
|
191
|
+
assert200(response)
|
|
192
|
+
assert len(response.json["data"]) == 2 # Only the owned reuses
|
|
193
|
+
assert reuse_in_response(response, private_owned)
|
|
194
|
+
assert reuse_in_response(response, private_owned_through_org)
|
|
195
|
+
assert not reuse_in_response(response, private_not_owned)
|
|
196
|
+
|
|
197
|
+
def test_reuse_api_list_filter_private_only_owned_by_user_no_user(self, api) -> None:
|
|
198
|
+
"""Shouldn't return any private reuses for non logged in users."""
|
|
199
|
+
user = UserFactory()
|
|
200
|
+
member = Member(user=user, role="editor")
|
|
201
|
+
org = OrganizationFactory(members=[member])
|
|
202
|
+
public_owned: Reuse = ReuseFactory(owner=user)
|
|
203
|
+
public_not_owned: Reuse = ReuseFactory()
|
|
204
|
+
_private_owned: Reuse = ReuseFactory(private=True, owner=user)
|
|
205
|
+
_private_owned_through_org: Reuse = ReuseFactory(private=True, organization=org)
|
|
206
|
+
_private_not_owned: Reuse = ReuseFactory(private=True)
|
|
207
|
+
|
|
208
|
+
response: TestResponse = api.get(url_for("api.reuses"))
|
|
209
|
+
assert200(response)
|
|
210
|
+
assert len(response.json["data"]) == 2
|
|
211
|
+
assert reuse_in_response(response, public_owned)
|
|
212
|
+
assert reuse_in_response(response, public_not_owned)
|
|
213
|
+
|
|
214
|
+
# Still no private returned if `private=False`
|
|
215
|
+
response = api.get(url_for("api.reuses", private=False))
|
|
216
|
+
assert200(response)
|
|
217
|
+
assert len(response.json["data"]) == 2
|
|
218
|
+
assert reuse_in_response(response, public_owned)
|
|
219
|
+
assert reuse_in_response(response, public_not_owned)
|
|
220
|
+
|
|
221
|
+
# Still no private returned if `private=True`
|
|
222
|
+
response = api.get(url_for("api.reuses", private=True))
|
|
223
|
+
assert200(response)
|
|
224
|
+
assert len(response.json["data"]) == 0
|
|
225
|
+
|
|
121
226
|
def test_reuse_api_get(self, api):
|
|
122
227
|
"""It should fetch a reuse from the API"""
|
|
123
228
|
reuse = ReuseFactory()
|
udata/tests/test_cors.py
CHANGED
|
@@ -2,7 +2,8 @@ from datetime import datetime
|
|
|
2
2
|
|
|
3
3
|
from flask import url_for
|
|
4
4
|
|
|
5
|
-
from udata
|
|
5
|
+
from udata import assets
|
|
6
|
+
from udata.core.dataset.factories import DatasetFactory, ResourceFactory
|
|
6
7
|
from udata.tests.api import APITestCase
|
|
7
8
|
from udata.tests.helpers import assert_status
|
|
8
9
|
|
|
@@ -10,6 +11,41 @@ from udata.tests.helpers import assert_status
|
|
|
10
11
|
class CorsTest(APITestCase):
|
|
11
12
|
modules = []
|
|
12
13
|
|
|
14
|
+
def test_cors_on_allowed_routes(self):
|
|
15
|
+
cors_headers = {
|
|
16
|
+
"Origin": "http://localhost",
|
|
17
|
+
"Access-Control-Request-Method": "GET",
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
dataset = DatasetFactory(resources=[ResourceFactory()])
|
|
21
|
+
|
|
22
|
+
# API Swagger
|
|
23
|
+
response = self.get(url_for("api.specs"), headers=cors_headers)
|
|
24
|
+
assert_status(response, 200)
|
|
25
|
+
assert "Access-Control-Allow-Origin" in response.headers
|
|
26
|
+
|
|
27
|
+
# API Dataset
|
|
28
|
+
response = self.get(url_for("api.dataset", dataset=dataset.id), headers=cors_headers)
|
|
29
|
+
assert_status(response, 200)
|
|
30
|
+
assert "Access-Control-Allow-Origin" in response.headers
|
|
31
|
+
|
|
32
|
+
# Resource permalink
|
|
33
|
+
response = self.get(f"/fr/datasets/r/{dataset.resources[0].id}", headers=cors_headers)
|
|
34
|
+
assert_status(response, 404) # The route is defined in udata-front
|
|
35
|
+
assert "Access-Control-Allow-Origin" in response.headers
|
|
36
|
+
|
|
37
|
+
# Oauth
|
|
38
|
+
response = self.get("/oauth/", headers=cors_headers)
|
|
39
|
+
assert_status(response, 404) # Oauth is defined in udata-front
|
|
40
|
+
assert "Access-Control-Allow-Origin" in response.headers
|
|
41
|
+
|
|
42
|
+
# Static
|
|
43
|
+
response = self.get(
|
|
44
|
+
assets.cdn_for("static", filename="my_static.css"), headers=cors_headers
|
|
45
|
+
)
|
|
46
|
+
assert_status(response, 404) # Not available in APITestCase
|
|
47
|
+
assert "Access-Control-Allow-Origin" in response.headers
|
|
48
|
+
|
|
13
49
|
def test_cors_redirects(self):
|
|
14
50
|
dataset = DatasetFactory(title="Old title")
|
|
15
51
|
old_slug = dataset.slug
|
udata/tests/test_discussions.py
CHANGED
|
@@ -2,8 +2,11 @@ from datetime import datetime
|
|
|
2
2
|
|
|
3
3
|
import pytest
|
|
4
4
|
from flask import url_for
|
|
5
|
+
from werkzeug.test import TestResponse
|
|
5
6
|
|
|
7
|
+
from udata.core.dataservices.factories import DataserviceFactory
|
|
6
8
|
from udata.core.dataset.factories import DatasetFactory
|
|
9
|
+
from udata.core.discussions.factories import DiscussionFactory
|
|
7
10
|
from udata.core.discussions.metrics import update_discussions_metric # noqa
|
|
8
11
|
from udata.core.discussions.models import Discussion, Message
|
|
9
12
|
from udata.core.discussions.notifications import discussions_notifications
|
|
@@ -19,8 +22,11 @@ from udata.core.discussions.tasks import (
|
|
|
19
22
|
notify_new_discussion_comment,
|
|
20
23
|
)
|
|
21
24
|
from udata.core.organization.factories import OrganizationFactory
|
|
25
|
+
from udata.core.organization.models import Organization
|
|
26
|
+
from udata.core.reuse.factories import ReuseFactory
|
|
22
27
|
from udata.core.spam.signals import on_new_potential_spam
|
|
23
28
|
from udata.core.user.factories import AdminFactory, UserFactory
|
|
29
|
+
from udata.core.user.models import User
|
|
24
30
|
from udata.models import Dataset, Member
|
|
25
31
|
from udata.tests.helpers import capture_mails
|
|
26
32
|
from udata.utils import faker
|
|
@@ -358,6 +364,61 @@ class DiscussionsTest(APITestCase):
|
|
|
358
364
|
|
|
359
365
|
self.assertEqual(len(response.json["data"]), len(discussions))
|
|
360
366
|
|
|
367
|
+
def assertIdIn(self, json_data: dict, id_: str) -> None:
|
|
368
|
+
for item in json_data:
|
|
369
|
+
if item["id"] == id_:
|
|
370
|
+
return
|
|
371
|
+
self.fail(f"id {id_} not in {json_data}")
|
|
372
|
+
|
|
373
|
+
def test_list_discussions_org_does_not_exist(self) -> None:
|
|
374
|
+
response: TestResponse = self.get(url_for("api.discussions", org="bad org id"))
|
|
375
|
+
self.assert404(response)
|
|
376
|
+
|
|
377
|
+
def test_list_discussions_org(self) -> None:
|
|
378
|
+
organization: Organization = OrganizationFactory()
|
|
379
|
+
user: User = UserFactory()
|
|
380
|
+
_discussion: Discussion = DiscussionFactory(user=user)
|
|
381
|
+
dataset = DatasetFactory(organization=organization)
|
|
382
|
+
dataservice = DataserviceFactory(organization=organization)
|
|
383
|
+
reuse = ReuseFactory(organization=organization)
|
|
384
|
+
discussion_for_dataset: Discussion = DiscussionFactory(subject=dataset, user=user)
|
|
385
|
+
discussion_for_dataservice: Discussion = DiscussionFactory(subject=dataservice, user=user)
|
|
386
|
+
discussion_for_reuse: Discussion = DiscussionFactory(subject=reuse, user=user)
|
|
387
|
+
|
|
388
|
+
response: TestResponse = self.get(url_for("api.discussions", org=organization.id))
|
|
389
|
+
self.assert200(response)
|
|
390
|
+
self.assertEqual(len(response.json["data"]), 3)
|
|
391
|
+
self.assertIdIn(response.json["data"], str(discussion_for_dataset.id))
|
|
392
|
+
self.assertIdIn(response.json["data"], str(discussion_for_dataservice.id))
|
|
393
|
+
self.assertIdIn(response.json["data"], str(discussion_for_reuse.id))
|
|
394
|
+
|
|
395
|
+
def test_list_discussions_sort(self) -> None:
|
|
396
|
+
user: User = UserFactory()
|
|
397
|
+
sorting_keys_dict: dict = {
|
|
398
|
+
"title": ["aaa", "bbb"],
|
|
399
|
+
"created": ["2023-12-12", "2024-01-01"],
|
|
400
|
+
"closed": ["2023-12-12", "2024-01-01"],
|
|
401
|
+
}
|
|
402
|
+
for sorting_key, values in sorting_keys_dict.items():
|
|
403
|
+
discussion1: Discussion = DiscussionFactory(user=user, **{sorting_key: values[0]})
|
|
404
|
+
discussion2: Discussion = DiscussionFactory(user=user, **{sorting_key: values[1]})
|
|
405
|
+
|
|
406
|
+
response: TestResponse = self.get(url_for("api.discussions", sort=sorting_key))
|
|
407
|
+
self.assert200(response)
|
|
408
|
+
self.assertEqual(len(response.json["data"]), 2)
|
|
409
|
+
self.assertEqual(response.json["data"][0]["id"], str(discussion1.id))
|
|
410
|
+
self.assertEqual(response.json["data"][1]["id"], str(discussion2.id))
|
|
411
|
+
|
|
412
|
+
# Reverse sort
|
|
413
|
+
response: TestResponse = self.get(url_for("api.discussions", sort="-" + sorting_key))
|
|
414
|
+
self.assert200(response)
|
|
415
|
+
self.assertEqual(len(response.json["data"]), 2)
|
|
416
|
+
self.assertEqual(response.json["data"][0]["id"], str(discussion2.id))
|
|
417
|
+
self.assertEqual(response.json["data"][1]["id"], str(discussion1.id))
|
|
418
|
+
|
|
419
|
+
# Clean slate
|
|
420
|
+
Discussion.objects.delete()
|
|
421
|
+
|
|
361
422
|
def test_list_discussions_user(self):
|
|
362
423
|
dataset = DatasetFactory()
|
|
363
424
|
discussions = []
|
udata/tests/test_owned.py
CHANGED
|
@@ -3,14 +3,25 @@ from mongoengine import post_save
|
|
|
3
3
|
import udata.core.owned as owned
|
|
4
4
|
from udata.core.organization.factories import OrganizationFactory
|
|
5
5
|
from udata.core.organization.models import Organization
|
|
6
|
-
from udata.core.user.factories import UserFactory
|
|
6
|
+
from udata.core.user.factories import AdminFactory, UserFactory
|
|
7
7
|
from udata.core.user.models import User
|
|
8
|
+
from udata.models import Member
|
|
8
9
|
from udata.mongo import db
|
|
9
10
|
from udata.tests import DBTestMixin, TestCase
|
|
10
11
|
|
|
11
12
|
|
|
13
|
+
class CustomQuerySet(owned.OwnedQuerySet):
|
|
14
|
+
def visible(self):
|
|
15
|
+
return self(private__ne=True)
|
|
16
|
+
|
|
17
|
+
|
|
12
18
|
class Owned(owned.Owned, db.Document):
|
|
13
19
|
name = db.StringField()
|
|
20
|
+
private = db.BooleanField()
|
|
21
|
+
|
|
22
|
+
meta = {
|
|
23
|
+
"queryset_class": CustomQuerySet,
|
|
24
|
+
}
|
|
14
25
|
|
|
15
26
|
|
|
16
27
|
class OwnedPostSave(owned.Owned, db.Document):
|
|
@@ -166,3 +177,91 @@ class OwnedQuerysetTest(DBTestMixin, TestCase):
|
|
|
166
177
|
|
|
167
178
|
for owned_ in excluded:
|
|
168
179
|
self.assertNotIn(owned_, result)
|
|
180
|
+
|
|
181
|
+
def test_visible_by_user(self) -> None:
|
|
182
|
+
admin: User = AdminFactory()
|
|
183
|
+
user: User = UserFactory()
|
|
184
|
+
member = Member(user=user, role="editor")
|
|
185
|
+
other_user: User = UserFactory()
|
|
186
|
+
org: Organization = OrganizationFactory(members=[member])
|
|
187
|
+
other_org: Organization = OrganizationFactory()
|
|
188
|
+
owned_by_user: Owned = Owned.objects.create(owner=user, name="owned_by_user")
|
|
189
|
+
owned_by_org: Owned = Owned.objects.create(organization=org, name="owned_by_org")
|
|
190
|
+
owned_by_other_user: Owned = Owned.objects.create(
|
|
191
|
+
owner=other_user, name="owned_by_other_user"
|
|
192
|
+
)
|
|
193
|
+
owned_by_other_org: Owned = Owned.objects.create(
|
|
194
|
+
organization=other_org, name="owned_by_other_org"
|
|
195
|
+
)
|
|
196
|
+
private_owned_by_user: Owned = Owned.objects.create(
|
|
197
|
+
owner=user, private=True, name="private_owned_by_user"
|
|
198
|
+
)
|
|
199
|
+
private_owned_by_org: Owned = Owned.objects.create(
|
|
200
|
+
organization=org, private=True, name="private_owned_by_org"
|
|
201
|
+
)
|
|
202
|
+
private_owned_by_other_user: Owned = Owned.objects.create(
|
|
203
|
+
owner=other_user, private=True, name="private_owned_by_other_user"
|
|
204
|
+
)
|
|
205
|
+
private_owned_by_other_org: Owned = Owned.objects.create(
|
|
206
|
+
organization=other_org, private=True, name="private_owned_by_other_org"
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
visible_by_user: list[Owned] = [
|
|
210
|
+
owned_by_user,
|
|
211
|
+
owned_by_org,
|
|
212
|
+
owned_by_other_user,
|
|
213
|
+
owned_by_other_org,
|
|
214
|
+
private_owned_by_user,
|
|
215
|
+
private_owned_by_org,
|
|
216
|
+
]
|
|
217
|
+
visible_by_other_user: list[Owned] = [
|
|
218
|
+
private_owned_by_other_user,
|
|
219
|
+
private_owned_by_other_org,
|
|
220
|
+
]
|
|
221
|
+
|
|
222
|
+
# Admin can view everything.
|
|
223
|
+
result: owned.OwnedQuerySet = Owned.objects.visible_by_user(
|
|
224
|
+
admin, Owned.objects.visible()._query_obj
|
|
225
|
+
)
|
|
226
|
+
# 4 public + 1 private owned by user + 1 private owned by the user's org.
|
|
227
|
+
self.assertEqual(len(result), 8)
|
|
228
|
+
for owned_ in visible_by_user + visible_by_other_user:
|
|
229
|
+
self.assertIn(owned_, result)
|
|
230
|
+
|
|
231
|
+
result = Owned.objects.visible_by_user(user, Owned.objects.visible()._query_obj)
|
|
232
|
+
# 4 public + 1 private owned by user + 1 private owned by the user's org.
|
|
233
|
+
self.assertEqual(len(result), 6)
|
|
234
|
+
for owned_ in visible_by_user:
|
|
235
|
+
self.assertIn(owned_, result)
|
|
236
|
+
|
|
237
|
+
# `.visible_by_user` does not reset other queries.
|
|
238
|
+
result = Owned.objects(name="owned_by_user").visible_by_user(
|
|
239
|
+
user, Owned.objects.visible()._query_obj
|
|
240
|
+
)
|
|
241
|
+
self.assertEqual(len(result), 1)
|
|
242
|
+
self.assertIn(owned_by_user, result)
|
|
243
|
+
result = Owned.objects.visible_by_user(user, Owned.objects.visible()._query_obj).filter(
|
|
244
|
+
name="owned_by_user"
|
|
245
|
+
)
|
|
246
|
+
self.assertEqual(len(result), 1)
|
|
247
|
+
self.assertIn(owned_by_user, result)
|
|
248
|
+
|
|
249
|
+
result = Owned.objects(name="private_owned_by_user").visible_by_user(
|
|
250
|
+
user, Owned.objects.visible()._query_obj
|
|
251
|
+
)
|
|
252
|
+
self.assertEqual(len(result), 1)
|
|
253
|
+
self.assertIn(private_owned_by_user, result)
|
|
254
|
+
result = Owned.objects.visible_by_user(user, Owned.objects.visible()._query_obj).filter(
|
|
255
|
+
name="private_owned_by_user"
|
|
256
|
+
)
|
|
257
|
+
self.assertEqual(len(result), 1)
|
|
258
|
+
self.assertIn(private_owned_by_user, result)
|
|
259
|
+
|
|
260
|
+
result = Owned.objects(name="private_owned_by_other_user").visible_by_user(
|
|
261
|
+
user, Owned.objects.visible()._query_obj
|
|
262
|
+
)
|
|
263
|
+
self.assertEqual(len(result), 0)
|
|
264
|
+
result = Owned.objects.visible_by_user(user, Owned.objects.visible()._query_obj).filter(
|
|
265
|
+
name="private_owned_by_other_user"
|
|
266
|
+
)
|
|
267
|
+
self.assertEqual(len(result), 0)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: udata
|
|
3
|
-
Version: 9.
|
|
3
|
+
Version: 9.2.2.dev31715
|
|
4
4
|
Summary: Open data portal
|
|
5
5
|
Home-page: https://github.com/opendatateam/udata
|
|
6
6
|
Author: Opendata Team
|
|
@@ -140,7 +140,24 @@ It is collectively taken care of by members of the
|
|
|
140
140
|
|
|
141
141
|
## Current (in progress)
|
|
142
142
|
|
|
143
|
+
- Add a filter on organization and document sort parameters in the `/discussions` endpoint [#3147](https://github.com/opendatateam/udata/pull/3147)
|
|
144
|
+
- Move discussion catalog creation and add fields [#3152](https://github.com/opendatateam/udata/pull/3152) and [#3154](https://github.com/opendatateam/udata/pull/3154)
|
|
145
|
+
- Add resources formats and harvest remote_url on dataset catalog [#3159](https://github.com/opendatateam/udata/pull/3159)
|
|
146
|
+
|
|
147
|
+
## 9.2.1 (2024-09-23)
|
|
148
|
+
|
|
149
|
+
- Enable basic search on dataservices [#3148](https://github.com/opendatateam/udata/pull/3148)
|
|
150
|
+
|
|
151
|
+
## 9.2.0 (2024-09-13)
|
|
152
|
+
|
|
153
|
+
- Allow OAuth clients without secrets [#3138](https://github.com/opendatateam/udata/pull/3138)
|
|
154
|
+
- Add a `archived` button for datasets and reuses on frontend admin [#3104](https://github.com/opendatateam/udata/pull/3104)
|
|
155
|
+
- **breaking change** Return all the reuses available to a user on the /reuses endpoint, including the private and deleted ones they own [#3140](https://github.com/opendatateam/udata/pull/3140).
|
|
143
156
|
- Fix undelete reuse and dataservices [#3141](https://github.com/opendatateam/udata/pull/3141)
|
|
157
|
+
- Add a minimal publiccode.yml [#3144](https://github.com/opendatateam/udata/pull/3144)
|
|
158
|
+
- Fix the boolean filters in the API for the "new system" endpoints [#3139](https://github.com/opendatateam/udata/pull/3139)
|
|
159
|
+
- Update authlib dependency from 0.14.3 to 1.3.1 [#3135](https://github.com/opendatateam/udata/pull/3135)
|
|
160
|
+
- Add CORS on resource redirect [#3145](https://github.com/opendatateam/udata/pull/3145)
|
|
144
161
|
|
|
145
162
|
## 9.1.4 (2024-08-26)
|
|
146
163
|
|
|
@@ -156,8 +173,6 @@ It is collectively taken care of by members of the
|
|
|
156
173
|
- Always add Vary even for non CORS requests [#3132](https://github.com/opendatateam/udata/pull/3132)
|
|
157
174
|
- Add acronym in organization csv catalog [#3134](https://github.com/opendatateam/udata/pull/3134)
|
|
158
175
|
- Limit the number of user suggestions [#3131](https://github.com/opendatateam/udata/pull/3131)
|
|
159
|
-
- Update authlib dependency from 0.14.3 to 1.3.1 [#3135](https://github.com/opendatateam/udata/pull/3135)
|
|
160
|
-
- Fix the boolean filters in the API for the "new system" endpoints [#3139](https://github.com/opendatateam/udata/pull/3139)
|
|
161
176
|
|
|
162
177
|
## 9.1.3 (2024-08-01)
|
|
163
178
|
|