From b745779e155c6e60353ac78e1da21389af2409b3 Mon Sep 17 00:00:00 2001 From: Tom Demeranville Date: Fri, 18 Dec 2015 15:58:19 +0000 Subject: [PATCH 1/5] Added support for OAuth client credentials (application) flow Added support for multiple authentication schemes To use the client credentials, index.html needs to have 1. input boxes to recieve client id and client secret 2. handlers that intitialse OAuth correctly on change. For example, you could replace the explore and apikey inputs in the header with:
and add the following javascrip to handle updates within the initialisation block (replacing the apikey javascript): function updateOauth(){ initOAuth({ clientId: $('#input_clientId')[0].value, clientSecret: $('#input_clientSecret')[0].value, realm: "blank", appName: "blank" }); } $('#input_clientId').change(updateOauth); $('#input_clientSecret').change(updateOauth); --- lib/swagger-oauth.js | 94 +++++++++++++++++++++++++++++++------------- 1 file changed, 67 insertions(+), 27 deletions(-) diff --git a/lib/swagger-oauth.js b/lib/swagger-oauth.js index 9ce42427..f782281b 100644 --- a/lib/swagger-oauth.js +++ b/lib/swagger-oauth.js @@ -3,7 +3,6 @@ var popupMask; var popupDialog; var clientId; var realm; -var oauth2KeyName; var redirect_uri; var clientSecret; var scopeSeparator; @@ -19,7 +18,7 @@ function handleLogin() { for(key in defs) { var auth = defs[key]; if(auth.type === 'oauth2' && auth.scopes) { - oauth2KeyName = key; + //oauth2KeyName = key; var scope; if(Array.isArray(auth.scopes)) { // 1.2 support @@ -31,7 +30,7 @@ function handleLogin() { else { // 2.0 support for(scope in auth.scopes) { - scopes.push({scope: scope, description: auth.scopes[scope]}); + scopes.push({scope: scope, description: auth.scopes[scope], OAuthSchemeKey: key}); } } } @@ -61,12 +60,16 @@ function handleLogin() { ''].join('')); $(document.body).append(popupDialog); + //TODO: only display applicable scopes (will need to pass them into handleLogin) popup = popupDialog.find('ul.api-popup-scopes').empty(); for (i = 0; i < scopes.length; i ++) { scope = scopes[i]; - str = '
  • ' + '
  • ' + '
  • '; popup.append(str); @@ -104,9 +107,25 @@ function handleLogin() { var defaultRedirectUrl = host.protocol + '//' + host.host + pathname + '/o2c.html'; var redirectUrl = window.oAuthRedirectUrl || defaultRedirectUrl; var url = null; - - for (var key in authSchemes) { - if (authSchemes.hasOwnProperty(key)) { + var scopes = [] + var o = popup.find('input:checked'); + var OAuthSchemeKeys = []; + var state; + for(k =0; k < o.length; k++) { + var scope = $(o[k]).attr('scope'); + if (scopes.indexOf(scope) === -1) + scopes.push(scope); + var OAuthSchemeKey = $(o[k]).attr('oauthtype'); + if (OAuthSchemeKeys.indexOf(OAuthSchemeKey) === -1) + OAuthSchemeKeys.push(OAuthSchemeKey); + } + + //TODO: merge not replace if scheme is different from any existing + //(needs to be aware of schemes to do so correctly) + window.enabledScopes=scopes; + + for (var key in authSchemes) { + if (authSchemes.hasOwnProperty(key) && OAuthSchemeKeys.indexOf(key) != -1) { //only look at keys that match this scope. var flow = authSchemes[key].flow; if(authSchemes[key].type === 'oauth2' && flow && (flow === 'implicit' || flow === 'accessCode')) { @@ -114,7 +133,14 @@ function handleLogin() { url = dets.authorizationUrl + '?response_type=' + (flow === 'implicit' ? 'token' : 'code'); window.swaggerUi.tokenName = dets.tokenName || 'access_token'; window.swaggerUi.tokenUrl = (flow === 'accessCode' ? dets.tokenUrl : null); + state = key; } + else if(authSchemes[key].type === 'oauth2' && flow && (flow === 'application')) { + var dets = authSchemes[key]; + window.swaggerUi.tokenName = dets.tokenName || 'access_token'; + clientCredentialsFlow(scopes, dets.tokenUrl, key); + return; + } else if(authSchemes[key].grantTypes) { // 1.2 support var o = authSchemes[key].grantTypes; @@ -135,20 +161,6 @@ function handleLogin() { } } } - var scopes = [] - var o = $('.api-popup-scopes').find('input:checked'); - - for(k =0; k < o.length; k++) { - var scope = $(o[k]).attr('scope'); - - if (scopes.indexOf(scope) === -1) - scopes.push(scope); - } - - // Implicit auth recommends a state parameter. - var state = Math.random (); - - window.enabledScopes=scopes; redirect_uri = redirectUrl; @@ -213,7 +225,32 @@ function initOAuth(opts) { }); } +function clientCredentialsFlow(scopes, tokenUrl, OAuthSchemeKey) { + var params = { + 'client_id': clientId, + 'client_secret': clientSecret, + 'scope': scopes.join(' '), + 'grant_type': 'client_credentials' + } + $.ajax( + { + url : tokenUrl, + type: "POST", + data: params, + success:function(data, textStatus, jqXHR) + { + onOAuthComplete(data,OAuthSchemeKey); + }, + error: function(jqXHR, textStatus, errorThrown) + { + onOAuthComplete(""); + } + }); + + } + window.processOAuthCode = function processOAuthCode(data) { + var OAuthSchemeKey = data.state; var params = { 'client_id': clientId, 'code': data.code, @@ -232,7 +269,7 @@ window.processOAuthCode = function processOAuthCode(data) { data: params, success:function(data, textStatus, jqXHR) { - onOAuthComplete(data); + onOAuthComplete(data, OAuthSchemeKey); }, error: function(jqXHR, textStatus, errorThrown) { @@ -241,7 +278,7 @@ window.processOAuthCode = function processOAuthCode(data) { }); }; -window.onOAuthComplete = function onOAuthComplete(token) { +window.onOAuthComplete = function onOAuthComplete(token,OAuthSchemeKey) { if(token) { if(token.error) { var checkbox = $('input[type=checkbox],.secured') @@ -251,11 +288,14 @@ window.onOAuthComplete = function onOAuthComplete(token) { alert(token.error); } else { - var b = token[window.swaggerUi.tokenName]; + var b = token[window.swaggerUi.tokenName]; + if (!OAuthSchemeKey){ + OAuthSchemeKey = token.state; + } if(b){ // if all roles are satisfied var o = null; - $.each($('.auth .api-ic .api_information_panel'), function(k, v) { + $.each($('.auth .api_information_panel'), function(k, v) { var children = v; if(children && children.childNodes) { var requiredScopes = []; @@ -292,7 +332,7 @@ window.onOAuthComplete = function onOAuthComplete(token) { } } }); - window.swaggerUi.api.clientAuthorizations.add(oauth2KeyName, new SwaggerClient.ApiKeyAuthorization('Authorization', 'Bearer ' + b, 'header')); + window.swaggerUi.api.clientAuthorizations.add(OAuthSchemeKey, new SwaggerClient.ApiKeyAuthorization('Authorization', 'Bearer ' + b, 'header')); } } } From bfc33ec6cdc3e9ffad064bfda4c466c80bc6dd7d Mon Sep 17 00:00:00 2001 From: Tom Demeranville Date: Fri, 18 Dec 2015 16:11:25 +0000 Subject: [PATCH 2/5] Added support for client credentials (application) flow Added support for multiple authentication schemes To use the client credentials, index.html needs to have 1. input boxes to receive client id and client secret 2. handlers that intitialise OAuth correctly on change. For example, you could replace the explore and apikey inputs in the header with:
    and add the following javascript to handle updates within the initialisation block (replacing the apikey javascript): function updateOauth(){ initOAuth({ clientId: $('#input_clientId')[0].value, clientSecret: $('#input_clientSecret')[0].value, realm: "blank", appName: "blank" }); } $('#input_clientId').change(updateOauth); $('#input_clientSecret').change(updateOauth); These examples could be incorporated into the swagger index.html file if required (or a new example created) Issues - displaying multiple schemes could be improved. Currently the UI is unaware that a particular scheme is required for a call so displays all scopes as options. Will require work on core swagger ui and templates to make scheme available to swagger-oauth.js --- lib/swagger-oauth.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/swagger-oauth.js b/lib/swagger-oauth.js index f782281b..171ea60b 100644 --- a/lib/swagger-oauth.js +++ b/lib/swagger-oauth.js @@ -18,7 +18,6 @@ function handleLogin() { for(key in defs) { var auth = defs[key]; if(auth.type === 'oauth2' && auth.scopes) { - //oauth2KeyName = key; var scope; if(Array.isArray(auth.scopes)) { // 1.2 support From 3f9b178ce1b5dc4cdebdd845280efbfaad63e4ff Mon Sep 17 00:00:00 2001 From: Tom Demeranville Date: Fri, 18 Dec 2015 16:21:01 +0000 Subject: [PATCH 3/5] fixed display of OAuth scheme in dialog if more than one scheme --- lib/swagger-oauth.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/swagger-oauth.js b/lib/swagger-oauth.js index 171ea60b..084f060e 100644 --- a/lib/swagger-oauth.js +++ b/lib/swagger-oauth.js @@ -65,10 +65,10 @@ function handleLogin() { scope = scopes[i]; str = '
  • ' + '
  • '; popup.append(str); From 04c23c4ee552f58f3f049b886bee596af52c9476 Mon Sep 17 00:00:00 2001 From: Tom Demeranville Date: Fri, 18 Dec 2015 16:24:07 +0000 Subject: [PATCH 4/5] added selector back in --- lib/swagger-oauth.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/swagger-oauth.js b/lib/swagger-oauth.js index 084f060e..576fcd33 100644 --- a/lib/swagger-oauth.js +++ b/lib/swagger-oauth.js @@ -294,7 +294,7 @@ window.onOAuthComplete = function onOAuthComplete(token,OAuthSchemeKey) { if(b){ // if all roles are satisfied var o = null; - $.each($('.auth .api_information_panel'), function(k, v) { + $.each($('.auth .api-ic .api_information_panel'), function(k, v) { var children = v; if(children && children.childNodes) { var requiredScopes = []; From 7fca3bdb4dbdd0e36569744964152d922038b93f Mon Sep 17 00:00:00 2001 From: Tom Demeranville Date: Fri, 18 Dec 2015 16:39:57 +0000 Subject: [PATCH 5/5] added changes to dist --- dist/lib/swagger-oauth.js | 93 +++++++++++++++++++++++++++------------ 1 file changed, 66 insertions(+), 27 deletions(-) diff --git a/dist/lib/swagger-oauth.js b/dist/lib/swagger-oauth.js index 9ce42427..576fcd33 100644 --- a/dist/lib/swagger-oauth.js +++ b/dist/lib/swagger-oauth.js @@ -3,7 +3,6 @@ var popupMask; var popupDialog; var clientId; var realm; -var oauth2KeyName; var redirect_uri; var clientSecret; var scopeSeparator; @@ -19,7 +18,6 @@ function handleLogin() { for(key in defs) { var auth = defs[key]; if(auth.type === 'oauth2' && auth.scopes) { - oauth2KeyName = key; var scope; if(Array.isArray(auth.scopes)) { // 1.2 support @@ -31,7 +29,7 @@ function handleLogin() { else { // 2.0 support for(scope in auth.scopes) { - scopes.push({scope: scope, description: auth.scopes[scope]}); + scopes.push({scope: scope, description: auth.scopes[scope], OAuthSchemeKey: key}); } } } @@ -61,12 +59,16 @@ function handleLogin() { ''].join('')); $(document.body).append(popupDialog); + //TODO: only display applicable scopes (will need to pass them into handleLogin) popup = popupDialog.find('ul.api-popup-scopes').empty(); for (i = 0; i < scopes.length; i ++) { scope = scopes[i]; - str = '
  • ' + '
  • ' + '
  • '; popup.append(str); @@ -104,9 +106,25 @@ function handleLogin() { var defaultRedirectUrl = host.protocol + '//' + host.host + pathname + '/o2c.html'; var redirectUrl = window.oAuthRedirectUrl || defaultRedirectUrl; var url = null; - - for (var key in authSchemes) { - if (authSchemes.hasOwnProperty(key)) { + var scopes = [] + var o = popup.find('input:checked'); + var OAuthSchemeKeys = []; + var state; + for(k =0; k < o.length; k++) { + var scope = $(o[k]).attr('scope'); + if (scopes.indexOf(scope) === -1) + scopes.push(scope); + var OAuthSchemeKey = $(o[k]).attr('oauthtype'); + if (OAuthSchemeKeys.indexOf(OAuthSchemeKey) === -1) + OAuthSchemeKeys.push(OAuthSchemeKey); + } + + //TODO: merge not replace if scheme is different from any existing + //(needs to be aware of schemes to do so correctly) + window.enabledScopes=scopes; + + for (var key in authSchemes) { + if (authSchemes.hasOwnProperty(key) && OAuthSchemeKeys.indexOf(key) != -1) { //only look at keys that match this scope. var flow = authSchemes[key].flow; if(authSchemes[key].type === 'oauth2' && flow && (flow === 'implicit' || flow === 'accessCode')) { @@ -114,7 +132,14 @@ function handleLogin() { url = dets.authorizationUrl + '?response_type=' + (flow === 'implicit' ? 'token' : 'code'); window.swaggerUi.tokenName = dets.tokenName || 'access_token'; window.swaggerUi.tokenUrl = (flow === 'accessCode' ? dets.tokenUrl : null); + state = key; } + else if(authSchemes[key].type === 'oauth2' && flow && (flow === 'application')) { + var dets = authSchemes[key]; + window.swaggerUi.tokenName = dets.tokenName || 'access_token'; + clientCredentialsFlow(scopes, dets.tokenUrl, key); + return; + } else if(authSchemes[key].grantTypes) { // 1.2 support var o = authSchemes[key].grantTypes; @@ -135,20 +160,6 @@ function handleLogin() { } } } - var scopes = [] - var o = $('.api-popup-scopes').find('input:checked'); - - for(k =0; k < o.length; k++) { - var scope = $(o[k]).attr('scope'); - - if (scopes.indexOf(scope) === -1) - scopes.push(scope); - } - - // Implicit auth recommends a state parameter. - var state = Math.random (); - - window.enabledScopes=scopes; redirect_uri = redirectUrl; @@ -213,7 +224,32 @@ function initOAuth(opts) { }); } +function clientCredentialsFlow(scopes, tokenUrl, OAuthSchemeKey) { + var params = { + 'client_id': clientId, + 'client_secret': clientSecret, + 'scope': scopes.join(' '), + 'grant_type': 'client_credentials' + } + $.ajax( + { + url : tokenUrl, + type: "POST", + data: params, + success:function(data, textStatus, jqXHR) + { + onOAuthComplete(data,OAuthSchemeKey); + }, + error: function(jqXHR, textStatus, errorThrown) + { + onOAuthComplete(""); + } + }); + + } + window.processOAuthCode = function processOAuthCode(data) { + var OAuthSchemeKey = data.state; var params = { 'client_id': clientId, 'code': data.code, @@ -232,7 +268,7 @@ window.processOAuthCode = function processOAuthCode(data) { data: params, success:function(data, textStatus, jqXHR) { - onOAuthComplete(data); + onOAuthComplete(data, OAuthSchemeKey); }, error: function(jqXHR, textStatus, errorThrown) { @@ -241,7 +277,7 @@ window.processOAuthCode = function processOAuthCode(data) { }); }; -window.onOAuthComplete = function onOAuthComplete(token) { +window.onOAuthComplete = function onOAuthComplete(token,OAuthSchemeKey) { if(token) { if(token.error) { var checkbox = $('input[type=checkbox],.secured') @@ -251,11 +287,14 @@ window.onOAuthComplete = function onOAuthComplete(token) { alert(token.error); } else { - var b = token[window.swaggerUi.tokenName]; + var b = token[window.swaggerUi.tokenName]; + if (!OAuthSchemeKey){ + OAuthSchemeKey = token.state; + } if(b){ // if all roles are satisfied var o = null; - $.each($('.auth .api-ic .api_information_panel'), function(k, v) { + $.each($('.auth .api-ic .api_information_panel'), function(k, v) { var children = v; if(children && children.childNodes) { var requiredScopes = []; @@ -292,7 +331,7 @@ window.onOAuthComplete = function onOAuthComplete(token) { } } }); - window.swaggerUi.api.clientAuthorizations.add(oauth2KeyName, new SwaggerClient.ApiKeyAuthorization('Authorization', 'Bearer ' + b, 'header')); + window.swaggerUi.api.clientAuthorizations.add(OAuthSchemeKey, new SwaggerClient.ApiKeyAuthorization('Authorization', 'Bearer ' + b, 'header')); } } }