swagger-ui-init.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  1. // swagger-ui-init.js
  2. // https://github.com/axnsan12/drf-yasg
  3. // Copyright 2017 - 2021, Cristian V. <cristi@cvjd.me>
  4. // This file is licensed under the BSD 3-Clause License.
  5. // License text available at https://opensource.org/licenses/BSD-3-Clause
  6. "use strict";
  7. var currentPath = window.location.protocol + "//" + window.location.host + window.location.pathname;
  8. var defaultSpecUrl = currentPath + '?format=openapi';
  9. function slugify(text) {
  10. return text.toString().toLowerCase()
  11. .replace(/\s+/g, '-') // Replace spaces with -
  12. .replace(/[^\w\-]+/g, '') // Remove all non-word chars
  13. .replace(/--+/g, '-') // Replace multiple - with single -
  14. .replace(/^-+/, '') // Trim - from start of text
  15. .replace(/-+$/, ''); // Trim - from end of text
  16. }
  17. var KEY_AUTH = slugify(window.location.pathname) + "-drf-yasg-auth";
  18. // load the saved authorization state from localStorage; ImmutableJS is used for consistency with swagger-ui state
  19. var savedAuth = Immutable.fromJS({});
  20. // global SwaggerUI config object; can be changed directly or by hooking initSwaggerUiConfig
  21. var swaggerUiConfig = {
  22. url: defaultSpecUrl,
  23. dom_id: '#swagger-ui',
  24. displayRequestDuration: true,
  25. presets: [
  26. SwaggerUIBundle.presets.apis,
  27. SwaggerUIStandalonePreset
  28. ],
  29. plugins: [
  30. SwaggerUIBundle.plugins.DownloadUrl
  31. ],
  32. layout: "StandaloneLayout",
  33. filter: true,
  34. requestInterceptor: function (request) {
  35. var headers = request.headers || {};
  36. var csrftoken = document.querySelector("[name=csrfmiddlewaretoken]");
  37. if (csrftoken) {
  38. headers["X-CSRFToken"] = csrftoken.value;
  39. }
  40. return request;
  41. }
  42. };
  43. function patchSwaggerUi() {
  44. if (document.querySelector('.auth-wrapper #django-session-auth')) {
  45. return;
  46. }
  47. var authWrapper = document.querySelector('.auth-wrapper');
  48. var authorizeButton = document.querySelector('.auth-wrapper .authorize');
  49. var djangoSessionAuth = document.querySelector('#django-session-auth');
  50. if (!djangoSessionAuth) {
  51. console.log("WARNING: session auth disabled");
  52. return;
  53. }
  54. djangoSessionAuth = djangoSessionAuth.cloneNode(true);
  55. authWrapper.insertBefore(djangoSessionAuth, authorizeButton);
  56. djangoSessionAuth.classList.remove("hidden");
  57. }
  58. function initSwaggerUi() {
  59. if (window.ui) {
  60. console.log("WARNING: skipping initSwaggerUi() because window.ui is already defined");
  61. return;
  62. }
  63. if (document.querySelector('.auth-wrapper .authorize')) {
  64. patchSwaggerUi();
  65. } else {
  66. insertionQ('.auth-wrapper .authorize').every(patchSwaggerUi);
  67. }
  68. var swaggerSettings = JSON.parse(document.getElementById('swagger-settings').innerHTML);
  69. var oauth2RedirectUrl = document.getElementById('oauth2-redirect-url');
  70. if (oauth2RedirectUrl) {
  71. if (!('oauth2RedirectUrl' in swaggerSettings)) {
  72. if (oauth2RedirectUrl) {
  73. swaggerSettings['oauth2RedirectUrl'] = oauth2RedirectUrl.href;
  74. }
  75. }
  76. oauth2RedirectUrl.parentNode.removeChild(oauth2RedirectUrl);
  77. }
  78. console.log('swaggerSettings', swaggerSettings);
  79. var oauth2Config = JSON.parse(document.getElementById('oauth2-config').innerHTML);
  80. console.log('oauth2Config', oauth2Config);
  81. initSwaggerUiConfig(swaggerSettings, oauth2Config);
  82. window.ui = SwaggerUIBundle(swaggerUiConfig);
  83. window.ui.initOAuth(oauth2Config);
  84. }
  85. /**
  86. * Initialize the global swaggerUiConfig with any given additional settings.
  87. * @param swaggerSettings SWAGGER_SETTINGS from Django settings
  88. * @param oauth2Settings OAUTH2_CONFIG from Django settings
  89. */
  90. function initSwaggerUiConfig(swaggerSettings, oauth2Settings) {
  91. var persistAuth = swaggerSettings.persistAuth;
  92. var refetchWithAuth = swaggerSettings.refetchWithAuth;
  93. var refetchOnLogout = swaggerSettings.refetchOnLogout;
  94. var fetchSchemaWithQuery = swaggerSettings.fetchSchemaWithQuery;
  95. delete swaggerSettings['persistAuth'];
  96. delete swaggerSettings['refetchWithAuth'];
  97. delete swaggerSettings['refetchOnLogout'];
  98. delete swaggerSettings['fetchSchemaWithQuery'];
  99. for (var p in swaggerSettings) {
  100. if (swaggerSettings.hasOwnProperty(p)) {
  101. swaggerUiConfig[p] = swaggerSettings[p];
  102. }
  103. }
  104. var specURL = swaggerUiConfig.url;
  105. if (fetchSchemaWithQuery) {
  106. // only add query params from document for the first spec request
  107. // this ensures we otherwise honor the spec selector box which might be manually modified
  108. var query = new URLSearchParams(window.location.search || '').entries();
  109. for (var it = query.next(); !it.done; it = query.next()) {
  110. specURL = setQueryParam(specURL, it.value[0], it.value[1]);
  111. }
  112. }
  113. if (persistAuth) {
  114. try {
  115. savedAuth = Immutable.fromJS(JSON.parse(localStorage.getItem(KEY_AUTH)) || {});
  116. } catch (e) {
  117. localStorage.removeItem(KEY_AUTH);
  118. }
  119. }
  120. if (refetchWithAuth) {
  121. specURL = applyAuth(savedAuth, specURL) || specURL;
  122. }
  123. swaggerUiConfig.url = specURL;
  124. if (persistAuth || refetchWithAuth) {
  125. var hookedAuth = false;
  126. var oldOnComplete = swaggerUiConfig.onComplete;
  127. swaggerUiConfig.onComplete = function () {
  128. if (persistAuth) {
  129. preauthorizeAll(savedAuth, window.ui);
  130. }
  131. if (!hookedAuth) {
  132. hookAuthActions(window.ui, persistAuth, refetchWithAuth, refetchOnLogout);
  133. hookedAuth = true;
  134. }
  135. if (oldOnComplete) {
  136. oldOnComplete();
  137. }
  138. };
  139. var specRequestsInFlight = {};
  140. var oldRequestInterceptor = swaggerUiConfig.requestInterceptor;
  141. swaggerUiConfig.requestInterceptor = function (request) {
  142. var headers = request.headers || {};
  143. if (request.loadSpec) {
  144. var newUrl = request.url;
  145. if (refetchWithAuth) {
  146. newUrl = applyAuth(savedAuth, newUrl, headers) || newUrl;
  147. }
  148. if (newUrl !== request.url) {
  149. request.url = newUrl;
  150. if (window.ui) {
  151. // this visually updates the spec url before the request is done, i.e. while loading
  152. window.ui.specActions.updateUrl(request.url);
  153. } else {
  154. // setTimeout is needed here because the request interceptor can be called *during*
  155. // window.ui initialization (by the SwaggerUIBundle constructor)
  156. setTimeout(function () {
  157. window.ui.specActions.updateUrl(request.url);
  158. });
  159. }
  160. // need to manually remember requests for spec urls because
  161. // responseInterceptor has no reference to the request...
  162. var absUrl = new URL(request.url, currentPath);
  163. specRequestsInFlight[absUrl.href] = request.url;
  164. }
  165. }
  166. if (oldRequestInterceptor) {
  167. request = oldRequestInterceptor(request);
  168. }
  169. return request;
  170. };
  171. var oldResponseInterceptor = swaggerUiConfig.responseInterceptor;
  172. swaggerUiConfig.responseInterceptor = function (response) {
  173. var absUrl = new URL(response.url, currentPath);
  174. if (absUrl.href in specRequestsInFlight) {
  175. var setToUrl = specRequestsInFlight[absUrl.href];
  176. delete specRequestsInFlight[absUrl.href];
  177. if (response.ok) {
  178. // need setTimeout here because swagger-ui insists to call updateUrl
  179. // with the initial request url after the response...
  180. setTimeout(function () {
  181. var currentUrl = new URL(window.ui.specSelectors.url(), currentPath);
  182. if (currentUrl.href !== absUrl.href) {
  183. window.ui.specActions.updateUrl(setToUrl);
  184. }
  185. });
  186. }
  187. }
  188. if (oldResponseInterceptor) {
  189. response = oldResponseInterceptor(response);
  190. }
  191. return response;
  192. }
  193. }
  194. }
  195. function _usp(url, fn) {
  196. url = url.split('?');
  197. var usp = new URLSearchParams(url[1] || '');
  198. fn(usp);
  199. url[1] = usp.toString();
  200. return url[1] ? url.join('?') : url[0];
  201. }
  202. function setQueryParam(url, key, value) {
  203. return _usp(url, function (usp) {
  204. usp.set(key, value);
  205. });
  206. }
  207. function removeQueryParam(url, key) {
  208. return _usp(url, function (usp) {
  209. usp.delete(key);
  210. })
  211. }
  212. /**
  213. * Call sui.preauthorize### for all authorizations in authorization.
  214. * @param authorization authorization object {key => authScheme} saved from authActions.authorize
  215. * @param sui SwaggerUI or SwaggerUIBundle instance
  216. */
  217. function preauthorizeAll(authorization, sui) {
  218. authorization.valueSeq().forEach(function (authScheme) {
  219. var schemeName = authScheme.get("name"), schemeType = authScheme.getIn(["schema", "type"]);
  220. if (schemeType === "basic" && schemeName) {
  221. var username = authScheme.getIn(["value", "username"]);
  222. var password = authScheme.getIn(["value", "password"]);
  223. if (username && password) {
  224. sui.preauthorizeBasic(schemeName, username, password);
  225. }
  226. } else if (schemeType === "apiKey" && schemeName) {
  227. var key = authScheme.get("value");
  228. if (key) {
  229. sui.preauthorizeApiKey(schemeName, key);
  230. }
  231. } else {
  232. // TODO: OAuth2
  233. }
  234. });
  235. }
  236. /**
  237. * Manually apply auth headers from the given auth object.
  238. * @param {object} authorization authorization object {key => authScheme} saved from authActions.authorize
  239. * @param {string} requestUrl the request url
  240. * @param {object} requestHeaders target headers, modified in place by the function
  241. * @return string new request url
  242. */
  243. function applyAuth(authorization, requestUrl, requestHeaders) {
  244. authorization.valueSeq().forEach(function (authScheme) {
  245. requestHeaders = requestHeaders || {};
  246. var schemeName = authScheme.get("name"), schemeType = authScheme.getIn(["schema", "type"]);
  247. if (schemeType === "basic" && schemeName) {
  248. var username = authScheme.getIn(["value", "username"]);
  249. var password = authScheme.getIn(["value", "password"]);
  250. if (username && password) {
  251. requestHeaders["Authorization"] = "Basic " + btoa(username + ":" + password);
  252. }
  253. } else if (schemeType === "apiKey" && schemeName) {
  254. var _in = authScheme.getIn(["schema", "in"]), paramName = authScheme.getIn(["schema", "name"]);
  255. var key = authScheme.get("value");
  256. if (key && paramName) {
  257. if (_in === "header") {
  258. requestHeaders[paramName] = key;
  259. }
  260. if (_in === "query") {
  261. if (requestUrl) {
  262. requestUrl = setQueryParam(requestUrl, paramName, key);
  263. } else {
  264. console.warn("WARNING: cannot apply apiKey query parameter via interceptor");
  265. }
  266. }
  267. }
  268. } else {
  269. // TODO: OAuth2
  270. }
  271. });
  272. return requestUrl;
  273. }
  274. /**
  275. * Remove the given authorization scheme from the url.
  276. * @param {object} authorization authorization object {key => authScheme} containing schemes to deauthorize
  277. * @param {string} requestUrl request url
  278. * @return string new request url
  279. */
  280. function deauthUrl(authorization, requestUrl) {
  281. authorization.valueSeq().forEach(function (authScheme) {
  282. var schemeType = authScheme.getIn(["schema", "type"]);
  283. if (schemeType === "apiKey") {
  284. var _in = authScheme.getIn(["schema", "in"]), paramName = authScheme.getIn(["schema", "name"]);
  285. if (_in === "query" && requestUrl && paramName) {
  286. requestUrl = removeQueryParam(requestUrl, paramName);
  287. }
  288. } else {
  289. // TODO: OAuth2?
  290. }
  291. });
  292. return requestUrl;
  293. }
  294. /**
  295. * Hook the authorize and logout actions of SwaggerUI.
  296. * The hooks are used to persist authorization data and trigger schema refetch.
  297. * @param sui SwaggerUI or SwaggerUIBundle instance
  298. * @param {boolean} persistAuth true to save auth to local storage
  299. * @param {boolean} refetchWithAuth true to trigger schema fetch on login
  300. * @param {boolean} refetchOnLogout true to trigger schema fetch on logout
  301. */
  302. function hookAuthActions(sui, persistAuth, refetchWithAuth, refetchOnLogout) {
  303. if (!persistAuth && !refetchWithAuth) {
  304. // nothing to do
  305. return;
  306. }
  307. var originalAuthorize = sui.authActions.authorize;
  308. sui.authActions.authorize = function (authorization) {
  309. originalAuthorize(authorization);
  310. // authorization is map of scheme name to scheme object
  311. // need to use ImmutableJS because schema is already an ImmutableJS object
  312. var newAuths = Immutable.fromJS(authorization);
  313. savedAuth = savedAuth.merge(newAuths);
  314. if (refetchWithAuth) {
  315. var url = sui.specSelectors.url();
  316. url = applyAuth(savedAuth, url) || url;
  317. sui.specActions.updateUrl(url);
  318. sui.specActions.download();
  319. sui.authActions.showDefinitions(); // hide authorize dialog
  320. }
  321. if (persistAuth) {
  322. localStorage.setItem(KEY_AUTH, JSON.stringify(savedAuth.toJSON()));
  323. }
  324. };
  325. var originalLogout = sui.authActions.logout;
  326. sui.authActions.logout = function (authorization) {
  327. // stash logged out methods for use with deauthUrl
  328. var loggedOut = savedAuth.filter(function (val, key) {
  329. return authorization.indexOf(key) !== -1;
  330. }).mapEntries(function (entry) {
  331. return [entry[0], entry[1].set("value", null)]
  332. });
  333. // remove logged out methods from savedAuth
  334. savedAuth = savedAuth.filter(function (val, key) {
  335. return authorization.indexOf(key) === -1;
  336. });
  337. if (refetchWithAuth) {
  338. var url = sui.specSelectors.url();
  339. url = deauthUrl(loggedOut, url) || url;
  340. sui.specActions.updateUrl(url);
  341. sui.specActions.download(url);
  342. sui.authActions.showDefinitions(); // hide authorize dialog
  343. }
  344. if (persistAuth) {
  345. localStorage.setItem(KEY_AUTH, JSON.stringify(savedAuth.toJSON()));
  346. }
  347. originalLogout(authorization);
  348. };
  349. }
  350. window.addEventListener('load', initSwaggerUi);