Implement OAuth2 password flowbubble
@@ -119,7 +119,13 @@ SwaggerUi.Views.AuthView = Backbone.View.extend({ | |||
else if(auth.get('type') === 'oauth2' && flow && (flow === 'application')) { | |||
dets = auth.attributes; | |||
container.tokenName = dets.tokenName || 'access_token'; | |||
this.clientCredentialsFlow(scopes, dets.tokenUrl, container.OAuthSchemeKey); | |||
this.clientCredentialsFlow(scopes, dets, container.OAuthSchemeKey); | |||
return; | |||
} | |||
else if(auth.get('type') === 'oauth2' && flow && (flow === 'password')) { | |||
dets = auth.attributes; | |||
container.tokenName = dets.tokenName || 'access_token'; | |||
this.passwordFlow(scopes, dets, container.OAuthSchemeKey); | |||
return; | |||
} | |||
else if(auth.get('grantTypes')) { | |||
@@ -156,17 +162,40 @@ SwaggerUi.Views.AuthView = Backbone.View.extend({ | |||
}, | |||
// taken from lib/swagger-oauth.js | |||
clientCredentialsFlow: function (scopes, tokenUrl, OAuthSchemeKey) { | |||
var params = { | |||
'client_id': clientId, | |||
'client_secret': clientSecret, | |||
clientCredentialsFlow: function (scopes, oauth, OAuthSchemeKey) { | |||
this.accessTokenRequest(scopes, oauth, OAuthSchemeKey, 'client_credentials'); | |||
}, | |||
passwordFlow: function (scopes, oauth, OAuthSchemeKey) { | |||
this.accessTokenRequest(scopes, oauth, OAuthSchemeKey, 'password', { | |||
'username': oauth.username, | |||
'password': oauth.password | |||
}); | |||
}, | |||
accessTokenRequest: function (scopes, oauth, OAuthSchemeKey, grantType, params) { | |||
params = $.extend({}, { | |||
'scope': scopes.join(' '), | |||
'grant_type': 'client_credentials' | |||
}; | |||
'grant_type': grantType | |||
}, params); | |||
var headers= {}; | |||
switch (oauth.clientAuthenticationType) { | |||
case 'basic': | |||
headers.Authorization = 'Basic ' + btoa(oauth.clientId + ':' + oauth.clientSecret); | |||
break; | |||
case 'request-body': | |||
params.client_id = oauth.clientId; | |||
params.client_secret = oauth.clientSecret; | |||
break; | |||
} | |||
$.ajax({ | |||
url : tokenUrl, | |||
url : oauth.tokenUrl, | |||
type: 'POST', | |||
data: params, | |||
headers: headers, | |||
success: function (data) | |||
{ | |||
onOAuthComplete(data, OAuthSchemeKey); | |||
@@ -177,5 +206,4 @@ SwaggerUi.Views.AuthView = Backbone.View.extend({ | |||
} | |||
}); | |||
} | |||
}); |
@@ -2,7 +2,9 @@ | |||
SwaggerUi.Models.Oauth2Model = Backbone.Model.extend({ | |||
defaults: { | |||
scopes: {} | |||
scopes: {}, | |||
isPasswordFlow: false, | |||
clientAuthenticationType: 'none' | |||
}, | |||
initialize: function () { | |||
@@ -19,6 +21,13 @@ SwaggerUi.Models.Oauth2Model = Backbone.Model.extend({ | |||
attributes.scopes = scopes; | |||
this.attributes = attributes; | |||
} | |||
if (this.attributes && this.attributes.flow) { | |||
var flow = this.attributes.flow; | |||
this.set('isPasswordFlow', flow === 'password'); | |||
this.set('requireClientAuthentication', flow === 'application'); | |||
this.set('clientAuthentication', flow === 'password' || flow === 'application'); | |||
} | |||
this.on('change', this.validate); | |||
}, | |||
@@ -35,6 +44,16 @@ SwaggerUi.Models.Oauth2Model = Backbone.Model.extend({ | |||
validate: function () { | |||
var valid = false; | |||
if (this.get('isPasswordFlow') && | |||
(!this.get('username'))) { | |||
return false; | |||
} | |||
if (this.get('clientAuthenticationType') in ['basic', 'request-body'] && | |||
(!this.get('clientId'))) { | |||
return false; | |||
} | |||
var scp = this.get('scopes'); | |||
var idx = _.findIndex(scp, function (o) { | |||
return o.checked === true; | |||
@@ -2,11 +2,20 @@ | |||
SwaggerUi.Views.Oauth2View = Backbone.View.extend({ | |||
events: { | |||
'change .oauth-scope': 'scopeChange' | |||
'change .oauth-scope': 'scopeChange', | |||
'change .oauth-username': 'setUsername', | |||
'change .oauth-password': 'setPassword', | |||
'change .oauth-client-authentication-type': 'setClientAuthenticationType', | |||
'change .oauth-client-id': 'setClientId', | |||
'change .oauth-client-secret': 'setClientSecret' | |||
}, | |||
template: Handlebars.templates.oauth2, | |||
cls: { | |||
error: 'error' | |||
}, | |||
render: function () { | |||
this.$el.html(this.template(this.model.toJSON())); | |||
@@ -18,5 +27,57 @@ SwaggerUi.Views.Oauth2View = Backbone.View.extend({ | |||
var scope = $(e.target).data('scope'); | |||
this.model.setScopes(scope, val); | |||
}, | |||
setUsername: function (e) { | |||
var val= $(e.target).val(); | |||
this.model.set('username', val); | |||
if (val) { | |||
$(e.target).removeClass(this.cls.error); | |||
} | |||
}, | |||
setPassword: function (e) { | |||
this.model.set('password', $(e.target).val()); | |||
}, | |||
setClientAuthenticationType: function (e) { | |||
var type = $(e.target).val(); | |||
var $el = this.$el; | |||
this.model.set('clientAuthenticationType', type); | |||
switch(type) { | |||
case 'none': | |||
$el.find('.oauth-client-authentication').hide(); | |||
break; | |||
case 'basic': | |||
case 'request-body': | |||
$el.find('.oauth-client-id').removeClass(this.cls.error); | |||
$el.find('.oauth-client-authentication').show(); | |||
break; | |||
} | |||
}, | |||
setClientId: function (e) { | |||
var val = $(e.target).val(); | |||
this.model.set('clientId', val); | |||
if (val) { | |||
$(e.target).removeClass(this.cls.error); | |||
} | |||
}, | |||
setClientSecret: function (e) { | |||
this.model.set('clientSecret', $(e.target).val()); | |||
$(e.target).removeClass('error'); | |||
}, | |||
highlightInvalid: function () { | |||
if (!this.model.get('username')) { | |||
this.$el.find('.oauth-username').addClass(this.cls.error); | |||
} | |||
if (!this.model.get('clientId')) { | |||
this.$el.find('.oauth-client-id').addClass(this.cls.error); | |||
} | |||
} | |||
}); |
@@ -201,4 +201,9 @@ | |||
} | |||
.api-popup-actions { padding-top: 10px; } | |||
fieldset { | |||
padding-bottom: 10px; | |||
padding-left: 20px; | |||
} | |||
} |
@@ -1,12 +1,36 @@ | |||
<div> | |||
<h3 class="auth__title">Select OAuth2.0 Scopes</h3> | |||
<h3 class="auth__title">OAuth2.0</h3> | |||
<p>{{{sanitize description}}}</p> | |||
{{#if authorizationUrl}}<p>Authorization URL: {{{sanitize authorizationUrl}}}</p>{{/if}} | |||
{{#if tokenUrl}}<p>Token URL: {{{sanitize tokenUrl}}}</p>{{/if}} | |||
<p>flow: {{{escape flow}}}</p> | |||
{{#if isPasswordFlow}} | |||
<p>Please input username and password for password flow authorization</p> | |||
<fieldset> | |||
<div><label>Username: <input class="oauth-username" type="text" name="username"></label></div> | |||
<div><label>Password: <input class="oauth-password" type="password" name="password"></label></div> | |||
</fieldset> | |||
{{/if}} | |||
{{#if clientAuthentication}} | |||
<p>Setup client authentication.{{#if requireClientAuthenticaiton}}(Required){{/if}}</p> | |||
<fieldset> | |||
<div><label>Type: | |||
<select class="oauth-client-authentication-type" name="client-authentication-type"> | |||
<option value="none" selected>None or other</option> | |||
<option value="basic">Basic auth</option> | |||
<option value="request-body">Request body</option> | |||
</select> | |||
</label></div> | |||
<div class="oauth-client-authentication" hidden> | |||
<div><label>ClientId: <input class="oauth-client-id" type="text" name="client-id"></label></div> | |||
<div><label>Secret: <input class="oauth-client-secret" type="text" name="client-secret"></label></div> | |||
</div> | |||
</fieldset> | |||
{{/if}} | |||
<p><strong> {{{escape appName}}} </strong> API requires the following scopes. Select which ones you want to grant to Swagger UI.</p> | |||
<p>Scopes are used to grant an application different levels of access to data on behalf of the end user. Each API may declare one or more scopes. | |||
<a href="#">Learn how to use</a> | |||
</p> | |||
<p><strong> {{{escape appName}}} </strong> API requires the following scopes. Select which ones you want to grant to Swagger UI.</p> | |||
<p>Authorization URL: {{{sanitize authorizationUrl}}}</p> | |||
<p>flow: {{{escape flow}}}</p> | |||
<ul class="api-popup-scopes"> | |||
{{#each scopes}} | |||
<li> | |||