Add auth hook settings and apiKey query support
This commit is contained in:
1 parent
5d8c936956
commit
060fe1881a
7 files changed
+206
-31
No files matched your search
@@ -42,6 +42,9 @@ SWAGGER_DEFAULTS = {
|
||||
'LOGOUT_URL': getattr(settings, 'LOGOUT_URL', None),
|
||||
'SPEC_URL': None,
|
||||
'VALIDATOR_URL': '',
|
||||
'PERSIST_AUTH': False,
|
||||
'REFETCH_SCHEMA_WITH_AUTH': False,
|
||||
'REFETCH_SCHEMA_ON_LOGOUT': False,
|
||||
|
||||
'OPERATIONS_SORTER': None,
|
||||
'TAGS_SORTER': None,
|
||||
|
||||
@@ -134,6 +134,9 @@ class SwaggerUIRenderer(_UIRenderer):
|
||||
'oauth2RedirectUrl': swagger_settings.OAUTH2_REDIRECT_URL,
|
||||
'supportedSubmitMethods': swagger_settings.SUPPORTED_SUBMIT_METHODS,
|
||||
'displayOperationId': swagger_settings.DISPLAY_OPERATION_ID,
|
||||
'persistAuth': swagger_settings.PERSIST_AUTH,
|
||||
'refetchWithAuth': swagger_settings.REFETCH_SCHEMA_WITH_AUTH,
|
||||
'refetchOnLogout': swagger_settings.REFETCH_SCHEMA_ON_LOGOUT,
|
||||
}
|
||||
|
||||
data = filter_none(data)
|
||||
|
||||
@@ -4,11 +4,6 @@ var defaultSpecUrl = currentPath + '?format=openapi';
|
||||
|
||||
// load the saved authorization state from localStorage; ImmutableJS is used for consistency with swagger-ui state
|
||||
var savedAuth = Immutable.fromJS({});
|
||||
try {
|
||||
savedAuth = Immutable.fromJS(JSON.parse(localStorage.getItem("drf-yasg-auth")) || {});
|
||||
} catch (e) {
|
||||
localStorage.removeItem("drf-yasg-auth");
|
||||
}
|
||||
|
||||
// global SwaggerUI config object; can be changed directly or by hooking initSwaggerUiConfig
|
||||
var swaggerUiConfig = {
|
||||
@@ -31,14 +26,7 @@ var swaggerUiConfig = {
|
||||
headers["X-CSRFToken"] = csrftoken.value;
|
||||
}
|
||||
|
||||
if (request.loadSpec) {
|
||||
applyAuth(savedAuth, headers);
|
||||
}
|
||||
return request;
|
||||
},
|
||||
onComplete: function () {
|
||||
preauthorizeAny(savedAuth, window.ui);
|
||||
hookAuthActions(window.ui);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -100,11 +88,85 @@ function initSwaggerUi() {
|
||||
* @param oauth2Settings OAUTH2_CONFIG from Django settings
|
||||
*/
|
||||
function initSwaggerUiConfig(swaggerSettings, oauth2Settings) {
|
||||
var persistAuth = swaggerSettings.persistAuth;
|
||||
var refetchWithAuth = swaggerSettings.refetchWithAuth;
|
||||
var refetchOnLogout = swaggerSettings.refetchOnLogout;
|
||||
delete swaggerSettings['persistAuth'];
|
||||
delete swaggerSettings['refetchWithAuth'];
|
||||
delete swaggerSettings['refetchOnLogout'];
|
||||
|
||||
for (var p in swaggerSettings) {
|
||||
if (swaggerSettings.hasOwnProperty(p)) {
|
||||
swaggerUiConfig[p] = swaggerSettings[p];
|
||||
}
|
||||
}
|
||||
|
||||
if (persistAuth || refetchWithAuth) {
|
||||
var hookedAuth = false;
|
||||
if (persistAuth) {
|
||||
try {
|
||||
savedAuth = Immutable.fromJS(JSON.parse(localStorage.getItem("drf-yasg-auth")) || {});
|
||||
} catch (e) {
|
||||
localStorage.removeItem("drf-yasg-auth");
|
||||
}
|
||||
}
|
||||
|
||||
var oldOnComplete = swaggerUiConfig.onComplete;
|
||||
swaggerUiConfig.onComplete = function () {
|
||||
if (persistAuth) {
|
||||
preauthorizeAny(savedAuth, window.ui);
|
||||
}
|
||||
|
||||
if (!hookedAuth) {
|
||||
hookAuthActions(window.ui, persistAuth, refetchWithAuth, refetchOnLogout);
|
||||
hookedAuth = true;
|
||||
}
|
||||
if (oldOnComplete) {
|
||||
oldOnComplete();
|
||||
}
|
||||
};
|
||||
|
||||
var specRequestsInFlight = [];
|
||||
var oldRequestInterceptor = swaggerUiConfig.requestInterceptor;
|
||||
swaggerUiConfig.requestInterceptor = function (request) {
|
||||
var headers = request.headers || {};
|
||||
if (refetchWithAuth && request.loadSpec) {
|
||||
var newUrl = applyAuth(savedAuth, request.url, headers) || request.url;
|
||||
if (newUrl !== request.url) {
|
||||
request.url = newUrl;
|
||||
}
|
||||
|
||||
// need to manually remember requests for spec urls because
|
||||
// responseInterceptor has no reference to the request...
|
||||
specRequestsInFlight.push(request.url);
|
||||
}
|
||||
|
||||
if (oldRequestInterceptor) {
|
||||
request = oldRequestInterceptor(request);
|
||||
}
|
||||
return request;
|
||||
};
|
||||
|
||||
var oldResponseInterceptor = swaggerUiConfig.responseInterceptor;
|
||||
swaggerUiConfig.responseInterceptor = function (response) {
|
||||
if (refetchWithAuth && specRequestsInFlight.indexOf(response.url) !== -1) {
|
||||
// need setTimeout here because swagger-ui insists to updateUrl with the initial request url...
|
||||
if (response.ok) {
|
||||
setTimeout(function () {
|
||||
window.ui.specActions.updateUrl(response.url);
|
||||
});
|
||||
}
|
||||
specRequestsInFlight = specRequestsInFlight.filter(function (val) {
|
||||
return val !== response.url;
|
||||
});
|
||||
}
|
||||
|
||||
if (oldResponseInterceptor) {
|
||||
response = oldResponseInterceptor(response);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -128,54 +190,126 @@ function preauthorizeAny(savedAuth, sui) {
|
||||
}
|
||||
}
|
||||
|
||||
function _usp(url, fn) {
|
||||
url = url.split('?');
|
||||
var usp = new URLSearchParams(url[1]);
|
||||
fn(usp);
|
||||
url[1] = usp.toString();
|
||||
return url.join('?');
|
||||
}
|
||||
|
||||
function addQueryParam(url, key, value) {
|
||||
return _usp(url, function (usp) {
|
||||
usp.set(key, value);
|
||||
})
|
||||
}
|
||||
|
||||
function removeQueryParam(url, key) {
|
||||
return _usp(url, function (usp) {
|
||||
usp.delete(key);
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually apply auth headers from the given auth object.
|
||||
* @param savedAuth auth object saved from authActions.authorize
|
||||
* @param requestHeaders target headers
|
||||
* @param {object} authScheme auth object saved from authActions.authorize
|
||||
* @param {string} requestUrl the request url
|
||||
* @param {object} requestHeaders target headers
|
||||
* @return string new request url
|
||||
*/
|
||||
function applyAuth(savedAuth, requestHeaders) {
|
||||
var schemeName = savedAuth.get("name"), schemeType = savedAuth.getIn(["schema", "type"]);
|
||||
function applyAuth(authScheme, requestUrl, requestHeaders) {
|
||||
requestHeaders = requestHeaders || {};
|
||||
var schemeName = authScheme.get("name"), schemeType = authScheme.getIn(["schema", "type"]);
|
||||
if (schemeType === "basic" && schemeName) {
|
||||
var username = savedAuth.getIn(["value", "username"]);
|
||||
var password = savedAuth.getIn(["value", "password"]);
|
||||
var username = authScheme.getIn(["value", "username"]);
|
||||
var password = authScheme.getIn(["value", "password"]);
|
||||
if (username && password) {
|
||||
requestHeaders["Authorization"] = "Basic " + btoa(username + ":" + password);
|
||||
}
|
||||
} else if (schemeType === "apiKey" && schemeName) {
|
||||
var key = savedAuth.get("value"), _in = savedAuth.getIn(["schema", "in"]);
|
||||
var paramName = savedAuth.getIn(["schema", "name"]);
|
||||
if (key && paramName && _in === "header") {
|
||||
requestHeaders[paramName] = key;
|
||||
}
|
||||
if (_in === "query") {
|
||||
console.warn("WARNING: cannot apply apiKey query parameter via interceptor");
|
||||
var _in = authScheme.getIn(["schema", "in"]), paramName = authScheme.getIn(["schema", "name"]);
|
||||
var key = authScheme.get("value");
|
||||
if (key && paramName) {
|
||||
if (_in === "header") {
|
||||
requestHeaders[paramName] = key;
|
||||
}
|
||||
if (_in === "query") {
|
||||
if (requestUrl) {
|
||||
requestUrl = addQueryParam(requestUrl, paramName, key);
|
||||
}
|
||||
else {
|
||||
console.warn("WARNING: cannot apply apiKey query parameter via interceptor");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return requestUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the given authorization scheme from the url.
|
||||
* @param {object} authScheme
|
||||
* @param {string} requestUrl
|
||||
*/
|
||||
function deauthUrl(authScheme, requestUrl) {
|
||||
var schemeType = authScheme.getIn(["schema", "type"]);
|
||||
if (schemeType === "apiKey") {
|
||||
var _in = authScheme.getIn(["schema", "in"]), paramName = authScheme.getIn(["schema", "name"]);
|
||||
if (_in === "query" && requestUrl && paramName) {
|
||||
requestUrl = removeQueryParam(requestUrl, paramName);
|
||||
}
|
||||
}
|
||||
return requestUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook the authorize and logout actions of SwaggerUI.
|
||||
* The hooks are used to persist authorization data and trigger schema refetch.
|
||||
* @param sui SwaggerUI or SwaggerUIBundle instance
|
||||
* @param {boolean} persistAuth true to save auth to local storage
|
||||
* @param {boolean} refetchWithAuth true to trigger schema fetch on login
|
||||
* @param {boolean} refetchOnLogout true to trigger schema fetch on logout
|
||||
*/
|
||||
function hookAuthActions(sui) {
|
||||
function hookAuthActions(sui, persistAuth, refetchWithAuth, refetchOnLogout) {
|
||||
if (!persistAuth && !refetchWithAuth) {
|
||||
// nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
var originalAuthorize = sui.authActions.authorize;
|
||||
sui.authActions.authorize = function (authorization) {
|
||||
originalAuthorize(authorization);
|
||||
// authorization is map of scheme name to scheme object
|
||||
// need to use ImmutableJS because schema is already an ImmutableJS object
|
||||
var schemes = Immutable.fromJS(authorization);
|
||||
var auth = schemes.valueSeq().first();
|
||||
localStorage.setItem("drf-yasg-auth", JSON.stringify(auth.toJSON()));
|
||||
savedAuth = auth;
|
||||
sui.specActions.download();
|
||||
savedAuth = schemes.valueSeq().first();
|
||||
|
||||
if (persistAuth) {
|
||||
localStorage.setItem("drf-yasg-auth", JSON.stringify(savedAuth.toJSON()));
|
||||
}
|
||||
|
||||
if (refetchWithAuth) {
|
||||
var url = sui.specSelectors.url();
|
||||
url = applyAuth(savedAuth, url) || url;
|
||||
sui.specActions.download(url);
|
||||
}
|
||||
};
|
||||
|
||||
var originalLogout = sui.authActions.logout;
|
||||
sui.authActions.logout = function (authorization) {
|
||||
if (savedAuth.get("name") === authorization[0]) {
|
||||
localStorage.removeItem("drf-yasg-auth");
|
||||
var oldAuth = savedAuth.set("value", null);
|
||||
savedAuth = Immutable.fromJS({});
|
||||
if (persistAuth) {
|
||||
localStorage.removeItem("drf-yasg-auth");
|
||||
}
|
||||
|
||||
if (refetchWithAuth) {
|
||||
var url = sui.specSelectors.url();
|
||||
url = deauthUrl(oldAuth, url) || url;
|
||||
sui.specActions.download(url);
|
||||
}
|
||||
}
|
||||
originalLogout(authorization);
|
||||
};
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
(function(t){var e=function(){try{return!!Symbol.iterator}catch(e){return false}};var r=e();var n=function(t){var e={next:function(){var e=t.shift();return{done:e===void 0,value:e}}};if(r){e[Symbol.iterator]=function(){return e}}return e};var i=function(e){return encodeURIComponent(e).replace(/%20/g,"+")};var a=function(e){return decodeURIComponent(e).replace(/\+/g," ")};var o=function(){var a=function(e){Object.defineProperty(this,"_entries",{writable:true,value:{}});if(typeof e==="string"){if(e!==""){this._fromString(e)}}else if(e instanceof a){var r=this;e.forEach(function(e,t){r.append(t,e)})}else if(e!==null&&typeof e==="object"){if(Object.prototype.toString.call(e)==="[object Array]"){for(var t=0;t<e.length;t++){var n=e[t];if(Object.prototype.toString.call(n)==="[object Array]"||n.length!==2){this.append(n[0],n[1])}else{throw new TypeError("Expected [string, any] as entry at index "+t+" of URLSearchParams's input")}}}else{for(var i in e){if(e.hasOwnProperty(i)){this.append(i,e[i])}}}}else{throw new TypeError("Unsupported input's type for URLSearchParams")}};var e=a.prototype;e.append=function(e,t){if(e in this._entries){this._entries[e].push(String(t))}else{this._entries[e]=[String(t)]}};e.delete=function(e){delete this._entries[e]};e.get=function(e){return e in this._entries?this._entries[e][0]:null};e.getAll=function(e){return e in this._entries?this._entries[e].slice(0):[]};e.has=function(e){return e in this._entries};e.set=function(e,t){this._entries[e]=[String(t)]};e.forEach=function(e,t){var r;for(var n in this._entries){if(this._entries.hasOwnProperty(n)){r=this._entries[n];for(var i=0;i<r.length;i++){e.call(t,r[i],n,this)}}}};e.keys=function(){var r=[];this.forEach(function(e,t){r.push(t)});return n(r)};e.values=function(){var t=[];this.forEach(function(e){t.push(e)});return n(t)};e.entries=function(){var r=[];this.forEach(function(e,t){r.push([t,e])});return n(r)};if(r){e[Symbol.iterator]=e.entries}e.toString=function(){var r=[];this.forEach(function(e,t){r.push(i(t)+"="+i(e))});return r.join("&")};t.URLSearchParams=a};if(!("URLSearchParams"in t)||new URLSearchParams("?a=1").toString()!=="a=1"){o()}var s=URLSearchParams.prototype;if(typeof s.sort!=="function"){s.sort=function(){var r=this;var n=[];this.forEach(function(e,t){n.push([t,e]);if(!r._entries){r.delete(t)}});n.sort(function(e,t){if(e[0]<t[0]){return-1}else if(e[0]>t[0]){return+1}else{return 0}});if(r._entries){r._entries={}}for(var e=0;e<n.length;e++){this.append(n[e][0],n[e][1])}}}if(typeof s._fromString!=="function"){Object.defineProperty(s,"_fromString",{enumerable:false,configurable:false,writable:false,value:function(e){if(this._entries){this._entries={}}else{var r=this;this.searchParams.forEach(function(e,t){r.delete(t)})}e=e.replace(/^\?/,"");var t=e.split("&");var n;for(var i=0;i<t.length;i++){n=t[i].split("=");this.append(a(n[0]),n.length>1?a(n[1]):"")}}})}})(typeof global!=="undefined"?global:typeof window!=="undefined"?window:typeof self!=="undefined"?self:this);(function(h){var e=function(){try{var e=new URL("b","http://a");e.pathname="c%20d";return e.href==="http://a/c%20d"&&e.searchParams}catch(e){return false}};var t=function(){var t=h.URL;var e=function(e,t){if(typeof e!=="string")e=String(e);var r=document,n;if(t&&(h.location===void 0||t!==h.location.href)){r=document.implementation.createHTMLDocument("");n=r.createElement("base");n.href=t;r.head.appendChild(n);try{if(n.href.indexOf(t)!==0)throw new Error(n.href)}catch(e){throw new Error("URL unable to set base "+t+" due to "+e)}}var i=r.createElement("a");i.href=e;if(n){r.body.appendChild(i);i.href=i.href}if(i.protocol===":"||!/:/.test(i.href)){throw new TypeError("Invalid URL")}Object.defineProperty(this,"_anchorElement",{value:i});var a=new URLSearchParams(this.search);var o=true;var s=true;var c=this;["append","delete","set"].forEach(function(e){var t=a[e];a[e]=function(){t.apply(a,arguments);if(o){s=false;c.search=a.toString();s=true}}});Object.defineProperty(this,"searchParams",{value:a,enumerable:true});var f=void 0;Object.defineProperty(this,"_updateSearchParams",{enumerable:false,configurable:false,writable:false,value:function(){if(this.search!==f){f=this.search;if(s){o=false;this.searchParams._fromString(this.search);o=true}}}})};var r=e.prototype;var n=function(t){Object.defineProperty(r,t,{get:function(){return this._anchorElement[t]},set:function(e){this._anchorElement[t]=e},enumerable:true})};["hash","host","hostname","port","protocol"].forEach(function(e){n(e)});Object.defineProperty(r,"search",{get:function(){return this._anchorElement["search"]},set:function(e){this._anchorElement["search"]=e;this._updateSearchParams()},enumerable:true});Object.defineProperties(r,{toString:{get:function(){var e=this;return function(){return e.href}}},href:{get:function(){return this._anchorElement.href.replace(/\?$/,"")},set:function(e){this._anchorElement.href=e;this._updateSearchParams()},enumerable:true},pathname:{get:function(){return this._anchorElement.pathnLine truncated
|
||||
@@ -43,6 +43,7 @@
|
||||
<script src="{% static 'drf-yasg/swagger-ui-dist/swagger-ui-standalone-preset.js' %}"></script>
|
||||
<script src="{% static 'drf-yasg/insQ.min.js' %}"></script>
|
||||
<script src="{% static 'drf-yasg/immutable.js' %}"></script>
|
||||
<script src="{% static 'drf-yasg/url-polyfill.min.js' %}"></script>
|
||||
<script src="{% static 'drf-yasg/swagger-ui-init.js' %}"></script>
|
||||
{% endblock %}
|
||||
{% block extra_scripts %}
|
||||
|
||||
Reference in new issue
Block a user