Add auth hook settings and apiKey query support
parent
5d8c936956
commit
060fe1881a
|
|
@ -222,6 +222,31 @@ set to ``None`` to remove the badge.
|
||||||
**Default**: :python:`'http://online.swagger.io/validator/'` |br|
|
**Default**: :python:`'http://online.swagger.io/validator/'` |br|
|
||||||
*Maps to parameter*: ``validatorUrl``
|
*Maps to parameter*: ``validatorUrl``
|
||||||
|
|
||||||
|
PERSIST_AUTH
|
||||||
|
------------
|
||||||
|
|
||||||
|
Persist swagger-ui authorization data to local storage. |br|
|
||||||
|
**WARNING:** this may be a security risk as the data is stored unencrypted
|
||||||
|
|
||||||
|
**Default**: :python:`'False` |br|
|
||||||
|
*Maps to parameter*: -
|
||||||
|
|
||||||
|
REFETCH_SCHEMA_WITH_AUTH
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
Re-fetch the OpenAPI document with the new credentials after authorization is performed through swagger-ui.
|
||||||
|
|
||||||
|
**Default**: :python:`'False` |br|
|
||||||
|
*Maps to parameter*: -
|
||||||
|
|
||||||
|
REFETCH_SCHEMA_ON_LOGOUT
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
Re-fetch the OpenAPI document without credentials after authorization is removed through swagger-ui.
|
||||||
|
|
||||||
|
**Default**: :python:`'False` |br|
|
||||||
|
*Maps to parameter*: -
|
||||||
|
|
||||||
OPERATIONS_SORTER
|
OPERATIONS_SORTER
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,9 @@ SWAGGER_DEFAULTS = {
|
||||||
'LOGOUT_URL': getattr(settings, 'LOGOUT_URL', None),
|
'LOGOUT_URL': getattr(settings, 'LOGOUT_URL', None),
|
||||||
'SPEC_URL': None,
|
'SPEC_URL': None,
|
||||||
'VALIDATOR_URL': '',
|
'VALIDATOR_URL': '',
|
||||||
|
'PERSIST_AUTH': False,
|
||||||
|
'REFETCH_SCHEMA_WITH_AUTH': False,
|
||||||
|
'REFETCH_SCHEMA_ON_LOGOUT': False,
|
||||||
|
|
||||||
'OPERATIONS_SORTER': None,
|
'OPERATIONS_SORTER': None,
|
||||||
'TAGS_SORTER': None,
|
'TAGS_SORTER': None,
|
||||||
|
|
|
||||||
|
|
@ -134,6 +134,9 @@ class SwaggerUIRenderer(_UIRenderer):
|
||||||
'oauth2RedirectUrl': swagger_settings.OAUTH2_REDIRECT_URL,
|
'oauth2RedirectUrl': swagger_settings.OAUTH2_REDIRECT_URL,
|
||||||
'supportedSubmitMethods': swagger_settings.SUPPORTED_SUBMIT_METHODS,
|
'supportedSubmitMethods': swagger_settings.SUPPORTED_SUBMIT_METHODS,
|
||||||
'displayOperationId': swagger_settings.DISPLAY_OPERATION_ID,
|
'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)
|
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
|
// load the saved authorization state from localStorage; ImmutableJS is used for consistency with swagger-ui state
|
||||||
var savedAuth = Immutable.fromJS({});
|
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
|
// global SwaggerUI config object; can be changed directly or by hooking initSwaggerUiConfig
|
||||||
var swaggerUiConfig = {
|
var swaggerUiConfig = {
|
||||||
|
|
@ -31,14 +26,7 @@ var swaggerUiConfig = {
|
||||||
headers["X-CSRFToken"] = csrftoken.value;
|
headers["X-CSRFToken"] = csrftoken.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request.loadSpec) {
|
|
||||||
applyAuth(savedAuth, headers);
|
|
||||||
}
|
|
||||||
return request;
|
return request;
|
||||||
},
|
|
||||||
onComplete: function () {
|
|
||||||
preauthorizeAny(savedAuth, window.ui);
|
|
||||||
hookAuthActions(window.ui);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -100,11 +88,85 @@ function initSwaggerUi() {
|
||||||
* @param oauth2Settings OAUTH2_CONFIG from Django settings
|
* @param oauth2Settings OAUTH2_CONFIG from Django settings
|
||||||
*/
|
*/
|
||||||
function initSwaggerUiConfig(swaggerSettings, oauth2Settings) {
|
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) {
|
for (var p in swaggerSettings) {
|
||||||
if (swaggerSettings.hasOwnProperty(p)) {
|
if (swaggerSettings.hasOwnProperty(p)) {
|
||||||
swaggerUiConfig[p] = swaggerSettings[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.
|
* Manually apply auth headers from the given auth object.
|
||||||
* @param savedAuth auth object saved from authActions.authorize
|
* @param {object} authScheme auth object saved from authActions.authorize
|
||||||
* @param requestHeaders target headers
|
* @param {string} requestUrl the request url
|
||||||
|
* @param {object} requestHeaders target headers
|
||||||
|
* @return string new request url
|
||||||
*/
|
*/
|
||||||
function applyAuth(savedAuth, requestHeaders) {
|
function applyAuth(authScheme, requestUrl, requestHeaders) {
|
||||||
var schemeName = savedAuth.get("name"), schemeType = savedAuth.getIn(["schema", "type"]);
|
requestHeaders = requestHeaders || {};
|
||||||
|
var schemeName = authScheme.get("name"), schemeType = authScheme.getIn(["schema", "type"]);
|
||||||
if (schemeType === "basic" && schemeName) {
|
if (schemeType === "basic" && schemeName) {
|
||||||
var username = savedAuth.getIn(["value", "username"]);
|
var username = authScheme.getIn(["value", "username"]);
|
||||||
var password = savedAuth.getIn(["value", "password"]);
|
var password = authScheme.getIn(["value", "password"]);
|
||||||
if (username && password) {
|
if (username && password) {
|
||||||
requestHeaders["Authorization"] = "Basic " + btoa(username + ":" + password);
|
requestHeaders["Authorization"] = "Basic " + btoa(username + ":" + password);
|
||||||
}
|
}
|
||||||
} else if (schemeType === "apiKey" && schemeName) {
|
} else if (schemeType === "apiKey" && schemeName) {
|
||||||
var key = savedAuth.get("value"), _in = savedAuth.getIn(["schema", "in"]);
|
var _in = authScheme.getIn(["schema", "in"]), paramName = authScheme.getIn(["schema", "name"]);
|
||||||
var paramName = savedAuth.getIn(["schema", "name"]);
|
var key = authScheme.get("value");
|
||||||
if (key && paramName && _in === "header") {
|
if (key && paramName) {
|
||||||
requestHeaders[paramName] = key;
|
if (_in === "header") {
|
||||||
}
|
requestHeaders[paramName] = key;
|
||||||
if (_in === "query") {
|
}
|
||||||
console.warn("WARNING: cannot apply apiKey query parameter via interceptor");
|
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.
|
* Hook the authorize and logout actions of SwaggerUI.
|
||||||
* The hooks are used to persist authorization data and trigger schema refetch.
|
* The hooks are used to persist authorization data and trigger schema refetch.
|
||||||
* @param sui SwaggerUI or SwaggerUIBundle instance
|
* @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;
|
var originalAuthorize = sui.authActions.authorize;
|
||||||
sui.authActions.authorize = function (authorization) {
|
sui.authActions.authorize = function (authorization) {
|
||||||
originalAuthorize(authorization);
|
originalAuthorize(authorization);
|
||||||
// authorization is map of scheme name to scheme object
|
// authorization is map of scheme name to scheme object
|
||||||
// need to use ImmutableJS because schema is already an ImmutableJS object
|
// need to use ImmutableJS because schema is already an ImmutableJS object
|
||||||
var schemes = Immutable.fromJS(authorization);
|
var schemes = Immutable.fromJS(authorization);
|
||||||
var auth = schemes.valueSeq().first();
|
savedAuth = schemes.valueSeq().first();
|
||||||
localStorage.setItem("drf-yasg-auth", JSON.stringify(auth.toJSON()));
|
|
||||||
savedAuth = auth;
|
if (persistAuth) {
|
||||||
sui.specActions.download();
|
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;
|
var originalLogout = sui.authActions.logout;
|
||||||
sui.authActions.logout = function (authorization) {
|
sui.authActions.logout = function (authorization) {
|
||||||
if (savedAuth.get("name") === authorization[0]) {
|
if (savedAuth.get("name") === authorization[0]) {
|
||||||
localStorage.removeItem("drf-yasg-auth");
|
var oldAuth = savedAuth.set("value", null);
|
||||||
savedAuth = Immutable.fromJS({});
|
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);
|
originalLogout(authorization);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -43,6 +43,7 @@
|
||||||
<script src="{% static 'drf-yasg/swagger-ui-dist/swagger-ui-standalone-preset.js' %}"></script>
|
<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/insQ.min.js' %}"></script>
|
||||||
<script src="{% static 'drf-yasg/immutable.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>
|
<script src="{% static 'drf-yasg/swagger-ui-init.js' %}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block extra_scripts %}
|
{% block extra_scripts %}
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,9 @@ REST_FRAMEWORK = {
|
||||||
SWAGGER_SETTINGS = {
|
SWAGGER_SETTINGS = {
|
||||||
'LOGIN_URL': reverse_lazy('admin:login'),
|
'LOGIN_URL': reverse_lazy('admin:login'),
|
||||||
'LOGOUT_URL': '/admin/logout',
|
'LOGOUT_URL': '/admin/logout',
|
||||||
|
'PERSIST_AUTH': True,
|
||||||
|
'REFETCH_SCHEMA_WITH_AUTH': True,
|
||||||
|
'REFETCH_SCHEMA_ON_LOGOUT': True,
|
||||||
|
|
||||||
'DEFAULT_INFO': 'testproj.urls.swagger_info',
|
'DEFAULT_INFO': 'testproj.urls.swagger_info',
|
||||||
|
|
||||||
|
|
@ -106,6 +109,11 @@ SWAGGER_SETTINGS = {
|
||||||
'type': 'apiKey',
|
'type': 'apiKey',
|
||||||
'name': 'Authorization',
|
'name': 'Authorization',
|
||||||
'in': 'header'
|
'in': 'header'
|
||||||
|
},
|
||||||
|
'Query': {
|
||||||
|
'type': 'apiKey',
|
||||||
|
'name': 'auth',
|
||||||
|
'in': 'query'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue