UNPKG

api-console-assets

Version:

This repo only exists to publish api console components to npm

1,303 lines (1,226 loc) 60.8 kB
<!-- @license Copyright 2016 The Advanced REST client authors <arc@mulesoft.com> Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> <link rel="import" href="../polymer/polymer.html"> <link rel="import" href="../paper-masked-input/paper-masked-input.html"> <link rel="import" href="../paper-icon-button/paper-icon-button.html"> <link rel="import" href="../paper-button/paper-button.html"> <link rel="import" href="../paper-input/paper-input.html"> <link rel="import" href="../arc-icons/arc-icons.html"> <link rel="import" href="../iron-icon/iron-icon.html"> <link rel="import" href="../paper-styles/paper-styles.html"> <link rel="import" href="../iron-flex-layout/iron-flex-layout.html"> <link rel="import" href="../iron-form/iron-form.html"> <link rel="import" href="../paper-item/paper-item.html"> <link rel="import" href="../paper-toast/paper-toast.html"> <link rel="import" href="../paper-dropdown-menu/paper-dropdown-menu.html"> <link rel="import" href="../paper-listbox/paper-listbox.html"> <link rel="import" href="../oauth2-scope-selector/oauth2-scope-selector.html"> <link rel="import" href="../paper-spinner/paper-spinner.html"> <link rel="import" href="../iron-collapse/iron-collapse.html"> <link rel="import" href="../paper-ripple/paper-ripple.html"> <link rel="import" href="../paper-tooltip/paper-tooltip.html"> <link rel="import" href="../paper-checkbox/paper-checkbox.html"> <link rel="import" href="../clipboard-copy/clipboard-copy.html"> <link rel="import" href="../arc-polyfills/arc-polyfills.html"> <link rel="import" href="../raml-type-form-behavior/raml-type-form-behavior.html"> <link rel="import" href="../raml-type-form-input/raml-type-form-input.html"> <link rel="import" href="../marked-element/marked-element.html"> <link rel="import" href="../markdown-styles/markdown-styles.html"> <link rel="import" href="auth-methods-behavior.html"> <!-- The `<auth-method-oauth2>` element displays a form to provide the OAuth 2.0 settings. ### Example ``` <auth-method-oauth2></auth-method-oauth2> ``` This element uses `oauth2-scope-selector` so the `allowedScopes`, `preventCustomScopes` and `scopes` properties will be set on this element. See documentation of `oauth2-scope-selector` for more description. ### Forcing the user to select scope from the list ```html <auth-method-oauth2 prevent-custom-scopes></auth-method-oauth2> ``` ```javascript var form = document.querySelector('auth-method-oauth2'); form.allowedScopes = ['profile', 'email']; ``` ## Authorizing the user The element sends the `oauth2-token-requested` with the OAuth settings provided with the form. Any element / app can handle this event and perform authorization. When the authorization is performed the app / other element should set back `tokenValue` property of this element or send the `oauth2-token-response` with token response value so the change will can reflected in the UI. ARC provides the `oauth2-authorization` element that can handle this events. ### Example ``` <auth-method-oauth2></auth-method-oauth2> <oauth2-authorization></oauth2-authorization> ``` The `oauth2-authorization` can be set anywhere in the DOM up from this element siblings to the body. See demo for example usage. ## Redirect URL Most OAuth 2 providers requires setting the redirect URL with the request. This can't be changed by the user and redirect URL can be only set in the provider's settings panel. The element accepts the `redirectUrl` property which will be displayed to the user that (s)he has to set up this callback URL in the OAuth provider settings. It can be any URL where token / code will be handled properly and the value returned to the `oauth2-authorization` element. See `oauth2-authorization` documentation for more information. If you going to use `oauth2-authorization` popup then the redirect URL value must be set to: `/bower_components/oauth-authorization/oauth-popup.html`. Mind missing `2` in `oauth-authorization`. This popup is a common popup for auth methods. ### OAuth 2.0 extensibility As per [RFC6749, section 8](https://tools.ietf.org/html/rfc6749#section-8) OAuth 2.0 protocol can be extended by custom `grant_type`, custom query parameters and custom headers. This is not yet supported in RAML. However, working together with RAML spec creators, an official RAML annotation to extend OAuth 2.0 settings has been created. The annotation source can be found in the [RAML organization repository](https://github.com/raml-org/raml-annotations/blob/master/annotations/security-schemes/oauth-2-custom-settings.raml). When the annotation is applied to the `ramlSettings` property, this element renders additional form inputs to support custom schemes. This produces additional property in the token authorization request: `customData`. The object contains user input from custom properties. #### `customData` model ```json customData: { auth: { parameters: Array|undefined }, token: { parameters: Array|undefined, headers: Array|undefined, body: Array|undefined } } ``` Each array item is an object with `name` and `value` property. `value` can be empty if the property is required. Otherwise it will be ignored. The `parameters` property contains all query parameters to be applied to the request. Query parameters can be applied to bothe token and authorization requests. The `headers` property contains all headers that can be applied to token request. `body` can be only applied to the token request. Note: body content type is always `application/x-www-form-urlencoded`. #### Annotation example ```yaml annotationTypes: customSettings: !include oauth-2-custom-settings.raml securitySchemes: oauth2: type: OAuth 2.0 describedBy: headers: Authorization: example: "Bearer token" settings: (customSettings): authorizationSettings: queryParameters: resource: type: string required: true description: | A resource ID that defines a domain of authorization. accessTokenSettings: body: resource: type: string required: true description: | A resource ID that defines a domain of authorization. accessTokenUri: https://auth.domain.com/authorize authorizationUri: https://auth.domain.com/token authorizationGrants: [code] scopes: profile ``` ### Styling `<auth-methods>` provides the following custom properties and mixins for styling: Custom property | Description | Default ----------------|-------------|---------- `--auth-method-oauth2` | Mixin applied to the element. | `{}` `--auth-method-panel` | Mixin applied to all auth elements. | `{}` `--auth-grant-dropdown` | Mixin applied to the authorization grants dropdown list | `{}` ### Theming Use this mixins as a theming option across all ARC elements. Custom property | Description | Default ----------------|-------------|---------- `--icon-button` | Mixin applied to `paper-icon-buttons`. | `{}` `--icon-button-hover` | Mixin applied to `paper-icon-buttons` when hovered. | `{}` `--input-line-color` | Mixin applied to the input underline | `{}` `--form-label` | Mixin applied to form labels. It will not affect `paper-*` labels | `{}` `--auth-button` | Mixin applied to authorization and next buttons` | `{}` `--auth-button-hover` | Mixin for :hover state for authorization and next buttons` | `{}` `--auth-button-disabled` | Mixin for disabled state for authorization and next buttons` | `{}` `--auth-redirect-section` | Mixin applied to the redirect uri section | `{}` @group UI Elements @element auth-method-oauth2 @demo demo/oauth2.html --> <dom-module id="auth-method-oauth2"> <template strip-whitespace> <style include="markdown-styles"></style> <style include="auth-methods-styles"> :host { display: block; @apply --auth-method-panel; @apply --auth-method-oauth2; --paper-icon-button: { color: var(--hint-trigger-color, rgba(0, 0, 0, 0.54)); transition: color 0.25s linear; @apply --icon-button; } --paper-icon-button-hover: { color: var(--hint-trigger-hover-color, rgba(0, 0, 0, 0.78)); @apply --icon-button-hover; } } .form { @apply --layout-flex; max-width: 700px; } oauth2-scope-selector { margin: 24px 0; outline: none; } .grant-dropdown { width: 320px; @apply --auth-grant-dropdown; } .auth-button { color: var(--accent-color); background-color: #fff; @apply --auth-button; } .auth-button:hover { @apply --auth-button-hover; } .auth-button[disabled] { background-color: rgba(0, 0, 0, 0.24); color: rgba(0, 0, 0, 0.54); @apply --auth-button-disabled; } .authorize-actions { margin-top: 12px; } .read-only-param-field { background-color: rgba(0, 0, 0, 0.12); @apply --arc-font-body1; display: block; white-space: pre-wrap; word-wrap: break-word; word-break: break-all; @apply --layout-horizontal; } .read-only-param-field.padding { padding: 12px; } .read-only-param-field.no-background { background-color: transparent; } label { @apply --form-label; } .token-info, .redirect-info { @apply --arc-font-body1; font-weight: 200; color: rgba(0, 0, 0, 0.54); } .code { @apply --arc-font-code1; @apply --layout-flex; } .token-label { font-weight: 500; font-size: 16px; } .current-token { margin-top: 24px; padding: 12px; border: 2px #3D8099 solid; } .current-token, .redirect-section, oauth2-scope-selector { max-width: 560px; width: calc(100% - 16px); box-sizing: border-box; } .redirect-section { @apply --auth-redirect-section; } *[hiddable] { display: none; } *[data-grant="authorization_code"] *[data-visible~="authorization_code"], *[data-grant="client_credentials"] *[data-visible~="client_credentials"], *[data-grant="implicit"] *[data-visible~="implicit"], *[data-grant="password"] *[data-visible~="password"] { display: block; } form[is-custom-grant] *[data-visible] { display: block !important; } .custom-data-field-value { @apply --layout-horizontal; @apply --layout-flex; } raml-type-form-input { @apply --layout-flex; } h4 { @apply --arc-font-subhead; } .help-icon { margin-top: 16px; } .markdown-html p:first-child { margin-top: 0; padding-top: 0; } .markdown-html p:last-child { margin-bottom: 0; padding-bottom: 0; } .custom-data-field-docs { @apply --arc-font-common-base; font-size: 13px !important; font-weight: 200; line-height: 24px; color: var(--inline-documentation-color, rgba(0, 0, 0, 0.87)); } </style> <form is="iron-form" id="form" data-grant$="[[grantType]]" is-custom-grant$="[[isCustomGrant]]"> <div class="row" hidden$="[[noGrantType]]"> <div class$="[[_computeStepperClass(isSelectedType)]]" on-tap="_clearTypeSelection"> <span class="step">[[_computeStep(stepStartIndex, 1)]]</span> <span class="step-header"> <span class="step-title"> Select OAuth 2.0 grant type <iron-icon icon="arc:edit" class="edit-icon" title="Click to make changes"></iron-icon> </span> <span class="step-selection" hidden$="[[!isSelectedType]]">[[_computeSelectedTypeLabel(grantType)]]</span> </span> <paper-ripple></paper-ripple> </div> <div class="step-content"> <div class="line"></div> <iron-collapse opened="[[typeSelectorOpened]]" class="content"> <div class="content"> <paper-dropdown-menu label="Grant type" class="grant-dropdown" required auto-validate> <paper-listbox class="dropdown-content" selected="{{grantType}}" attr-for-selected="data-type"> <template is="dom-repeat" items="[[grantTypes]]"> <paper-item data-type$="[[item.type]]">[[item.label]]</paper-item> </template> </paper-listbox> </paper-dropdown-menu> </div> </iron-collapse> </div> </div> <div class="row"> <div class="stepper"> <span class="step">[[_computeStep(stepStartIndex, 2)]]</span> <span class="step-header"> <span class="step-title">Set authorization data</span> </span> </div> <div class="step-content" id="authDataForm"> <div class="line"></div> <div class="content"> <paper-masked-input auto-validate required$="[[_isFieldRequired(isCustomGrant)]]" label="Client id" value="{{clientId}}" id="clientId" autocomplete="on"></paper-masked-input> <paper-tooltip for="clientId" position="bottom">Your client ID registered in your OAuth provider.</paper-tooltip> <paper-masked-input auto-validate required$="[[_isFieldRequired(isCustomGrant)]]" label="Client secret" value="{{clientSecret}}" id="clientSecret" hiddable data-visible="client_credentials authorization_code" disabled$="[[_isFieldDisabled(isCustomGrant, grantType, 'client_credentials', 'authorization_code')]]" autocomplete="on"></paper-masked-input> <paper-tooltip for="clientSecret" position="bottom">The client secret is generated by your provider unique string for your app. Check provider's console to get the code.</paper-tooltip> <template is="dom-if" if="[[authQueryParameters]]"> <h4>Authorization request query parameters</h4> <template is="dom-repeat" items="[[authQueryParameters]]" data-repeater="auth-query"> <div class="custom-data-field"> <div class="custom-data-field-value"> <raml-type-form-input model="[[item]]" name="[[item.name]]" value="{{item.value}}" data-type="auth-query"></raml-type-form-input> <template is="dom-if" if="[[item.hasDescription]]"> <span> <paper-icon-button class="help-icon" icon="arc:help" on-tap="_toggleDocumentation" data-source="auth-query"></paper-icon-button> <paper-tooltip position="left" offset="1" margin-top="1" animation-delay="300">Display documentation</paper-tooltip> </span> </template> </div> <div class="custom-data-field-docs" data-source="auth-query" data-key$="[[item.key]]"></div> </div> </template> </template> <template is="dom-if" if="[[tokenQueryParameters]]"> <h4>Token request query parameters</h4> <template is="dom-repeat" items="[[tokenQueryParameters]]" data-repeater="token-query"> <div class="custom-data-field"> <div class="custom-data-field-value"> <raml-type-form-input model="[[item]]" name="[[item.name]]" value="{{item.value}}" data-type="token-query"></raml-type-form-input> <template is="dom-if" if="[[item.hasDescription]]"> <span> <paper-icon-button class="help-icon" icon="arc:help" on-tap="_toggleDocumentation" data-source="token-query"></paper-icon-button> <paper-tooltip position="left" offset="1" margin-top="1" animation-delay="300">Display documentation</paper-tooltip> </span> </template> </div> <div class="custom-data-field-docs" data-source="token-query" data-key$="[[item.key]]"></div> </div> </template> </template> <template is="dom-if" if="[[tokenHeaders]]"> <h4>Token request headers</h4> <template is="dom-repeat" items="[[tokenHeaders]]" data-repeater="token-headers"> <div class="custom-data-field"> <div class="custom-data-field-value"> <raml-type-form-input model="[[item]]" name="[[item.name]]" value="{{item.value}}" data-type="token-headers"></raml-type-form-input> <template is="dom-if" if="[[item.hasDescription]]"> <span> <paper-icon-button class="help-icon" icon="arc:help" on-tap="_toggleDocumentation" data-source="token-headers"></paper-icon-button> <paper-tooltip position="left" offset="1" margin-top="1" animation-delay="300">Display documentation</paper-tooltip> </span> </template> </div> <div class="custom-data-field-docs" data-source="token-headers" data-key$="[[item.key]]"></div> </div> </template> </template> <template is="dom-if" if="[[tokenBody]]"> <h4>Token request body</h4> <template is="dom-repeat" items="[[tokenBody]]" data-repeater="token-body"> <div class="custom-data-field"> <div class="custom-data-field-value"> <raml-type-form-input model="[[item]]" name="[[item.name]]" value="{{item.value}}" data-type="token-body"></raml-type-form-input> <template is="dom-if" if="[[item.hasDescription]]"> <span> <paper-icon-button class="help-icon" icon="arc:help" on-tap="_toggleDocumentation" data-source="token-body"></paper-icon-button> <paper-tooltip position="left" offset="1" margin-top="1" animation-delay="300">Display documentation</paper-tooltip> </span> </template> </div> <div class="custom-data-field-docs" data-source="token-body" data-key$="[[item.key]]"></div> </div> </template> </template> <template is="dom-if" if="[[isAdvanced]]"> <paper-checkbox class="adv-settings-input" checked="{{advancedOpened}}">Advanced settings</paper-checkbox> </template> <iron-collapse opened="[[advancedOpened]]"> <paper-input auto-validate required$="[[_isFieldRequired(isCustomGrant)]]" label="Authorization url" value="{{authUrl}}" id="authUrl" hiddable data-visible="implicit authorization_code" disabled$="[[_isFieldDisabled(isCustomGrant, grantType, 'implicit', 'authorization_code')]]" type="text" autocomplete="on"> <paper-icon-button suffix on-tap="_clearField" icon="arc:clear" alt="Clear input icon" title="Clear input"></paper-icon-button> </paper-input> <paper-tooltip for="authUrl" position="bottom">The authorization URL initializes the OAuth flow. If you don't know the authorization URL check your provider's documentation.</paper-tooltip> <paper-input auto-validate required$="[[_isFieldRequired(isCustomGrant)]]" label="Access token URL" value="{{accessTokenUrl}}" id="accessTokenUrl" hiddable data-visible="client_credentials authorization_code password" disabled$="[[_isFieldDisabled(isCustomGrant, grantType, 'client_credentials', 'authorization_code', 'password')]]" type="text" autocomplete="on"> <paper-icon-button suffix on-tap="_clearField" icon="arc:clear" alt="Clear input icon" title="Clear input"></paper-icon-button> </paper-input> <paper-tooltip for="accessTokenUrl" position="bottom">The access token URL is used by server implementations to exchange access code for access token.</paper-tooltip> <paper-masked-input auto-validate required$="[[_isFieldRequired(isCustomGrant)]]" label="Username" value="{{username}}" id="username" hiddable data-visible="password" disabled$="[[_isFieldDisabled(isCustomGrant, grantType, 'password')]]" autocomplete="on"></paper-masked-input> <paper-tooltip for="username" position="bottom">The username required for this OAuth authentication.</paper-tooltip> <paper-masked-input auto-validate required$="[[_isFieldRequired(isCustomGrant)]]" label="Password" value="{{password}}" id="password" hiddable data-visible="password" disabled$="[[_isFieldDisabled(isCustomGrant, grantType, 'password')]]" autocomplete="on"></paper-masked-input> <paper-tooltip for="password" position="bottom">The password required for this OAuth authentication.</paper-tooltip> <div> <oauth2-scope-selector allowed-scopes="[[allowedScopes]]" prevent-custom-scopes="[[preventCustomScopes]]" scopes="{{scopes}}"></oauth2-scope-selector> </div> </iron-collapse> </div> </div> </div> <div class="row"> <div class="stepper"> <span class="step">[[_computeStep(stepStartIndex, 3)]]</span> <span class="step-header"> <span class="step-title">Set redirect URL</span> </span> </div> <div class="step-content"> <div class="line"></div> <div class="content"> <div class="redirect-section"> <p class="redirect-info">Set this redirect URL in OAuth 2.0 provider settings.</p> <p class="read-only-param-field padding"> <span class="code" tabindex="0" id="redirectLabel">[[redirectUrl]]</span> <paper-icon-button icon="arc:content-copy" on-tap="_copyContent" data-src="redirect" id="redirectCopyButton"></paper-icon-button> </p> </div> <div class="authorize-actions" hidden$="[[hasTokenValue]]"> <paper-button disabled$="[[_authorizing]]" class="auth-button" data-type="get-token" on-tap="authorize">Get access token</paper-button> <paper-spinner active="[[_authorizing]]"></paper-spinner> </div> <div class="current-token" hidden$="[[!hasTokenValue]]"> <label class="token-label">Current token</label> <p class="token-info">The token will be set to the corresponding field (header or query parameter).</p> <p class="read-only-param-field no-background"> <span class="code" tabindex="0" id="tokenLabel">[[tokenValue]]</span> <paper-icon-button icon="arc:content-copy" on-tap="_copyContent" data-src="token" id="tokenCopyButton"></paper-icon-button> </p> <div class="authorize-actions"> <paper-button disabled$="[[_authorizing]]" class="auth-button" data-type="refresh-token" on-tap="authorize">Refresh access token</paper-button> <paper-spinner active="[[_authorizing]]"></paper-spinner> </div> </div> </div> </div> </div> </form> <paper-toast text="" duration="5000"></paper-toast> <clipboard-copy></clipboard-copy> </template> <script> Polymer({ is: 'auth-method-oauth2', behaviors: [ ArcBehaviors.AuthMethodsBehavior, ArcBehaviors.RamlTypeFormBehavior ], /** * Fired when user requested to perform an authorization. * The details object vary depends on the `grantType` property. * However this event always fire two properties set on the `detail` object: `type` and * `clientId`. * * @event oauth2-token-requested * @param {String} type The type of grant option selected by the user. `implicit` is * the browser flow where token ir requested. `authorization_code` or server flow is where * client asks for the authorization code and exchange it later for the auth token using * client secret. Other options are `password` and `client_credentials`. * @param {String} clientId Every type requires `clientId`. * @param {String} authorizationUrl Token authorization URL. Used in `implicit` and * `authorization_code` types. In both cases means the initial endpoint to request for token * or the authorization code. * @param {Array<String>} scopes A list of scopes seleted by the user. Used in `implicit` * and `authorization_code` types. * @param {String} redirectUrl A redirect URL of the client after authorization (or error). * This must be set in the provider's OAuth settings. Callback URL must communicate with * the app to pass the information back to the application. User can't change the `redirectUrl` * but the app shouldn't rely on this value since in browser environment it is possible to * temper with variables. The `redirectUrl` must be set to this element by owner app (which * must know this value). A `redirectUrl` is set for `implicit` and `authorization_code` * types. * @param {String} clientSecret The client secret that user can get from the OAuth provider * settings console. User in `authorization_code` and `client_credentials` types. * @param {String} accessTokenUrl An URL to exchange code for the access token. Used by * `authorization_code`, `client_credentials` and `password` types. * @param {String} username Used with `password` type. * @param {String} password Used with `password` type. * @param {Object} customData Custom query parameters, headers and body applied * to the authorization or token request. See this element description for details. */ /** * Fired when the any of the auth method settings has changed. * This event will be fired quite frequently - each time anything in the text field changed. * With one exception. This event will not be fired if the validation of the form didn't passed. * * This event will set current settings as a detail object which are the same as for the * `oauth2-token-requested` event. Additionally it will contain a `tokenValue` property. This * valye can be `undefined` if token hasn't been requested yet by the user. * Clients should support a situaltion when the user do not request the token before requesting * the resource and perform authorization. * * @event auth-settings-changed * @param {Object} settings See the `oauth2-token-requested` for detailed * description * @param {String} type The authentication type selected by the user. * @param {Boolean} valid True if the form has been validated. */ /** * Fired when the request token has been obtained and it's ready to serve. * Because only one auth panel can be displayed ad a time it can be assumed * that if new token has been obtained then it is current authorization * method. * * @event oauth2-token-ready * @param {String} token The OAuth 2.0 token */ properties: { // Seleted authorization grand type. grantType: { type: String, value: '', notify: true }, /** * Computed value, true if the grant type is a cutom definition. */ isCustomGrant: { type: Boolean, computed: '_computeIsCustomGrant(grantType)', value: false }, // Computed value, true if the `grantType` is set. isSelectedType: { type: Boolean, value: false, computed: '_computeIsSelectedType(grantType)' }, /** * If true, OAuth flow selector will be collapsed. */ forceHideTypeSelector: { type: Boolean, value: false }, // Computed value, true if the type selector is allowd to be hidden typeSelectorOpened: { type: Boolean, value: false, // jscs:disable maximumLineLength computed: '_computeTypeSelectorOpened(isSelectedType, forceHideTypeSelector, _typeSelectorForceOpened)' // jscs:enable maximumLineLength }, // The client ID for the auth token. clientId: { type: String, notify: true, value: '' }, // The client secret. It to be used when selected server flow. clientSecret: { type: String, notify: true, value: '' }, // The authorization URL to initialize the OAuth flow. authUrl: { type: String, notify: true, value: '' }, // The access token URL to exchange code for token. It is used in server flow. accessTokenUrl: { type: String, notify: true, value: '' }, // The password. To be used with the password flow. password: { type: String, notify: true, value: '' }, // The password. To be used with the password flow. username: { type: String, notify: true, value: '' }, /** * A callback URL to be used with this element. * User can't change the callback URL and it will inform the user to setup OAuth to use * this value. * * This is relevant when selected flow is the browser flow. */ redirectUrl: String, /** * List of user selected scopes. * It can be pre-populated with list of scopes (array of strings). */ scopes: Array, /** * List of pre-defined scopes to choose from. It will be passed to the `oauth2-scope-selector` * element. */ allowedScopes: Array, /** * If true then the `oauth2-scope-selector` will disallow to add a scope that is not * in the `allowedScopes` list. Has no effect if the `allowedScopes` is not set. */ preventCustomScopes: Boolean, // True when currently authorizing the user. _authorizing: Boolean, /** * When the user authorized the app it should be set to the token value. * This element do not perform authorization. Other elements must intercept * `oauth2-token-requested` and perform the authorization. As a result the element * performing an authorization should set back the auth token on the event target object * (this element). */ tokenValue: { type: String, observer: '_tokenValueChanged', value: '' }, // Computed value, true if access token is set. hasTokenValue: { type: Boolean, value: false, readOnly: true }, /** * RAML `securedBy` obejct definition. * If set, it will prefill the settings in the auth panel. */ ramlSettings: Object, // List of all possible grant types; _supportedGrantTypes: { type: Array, value: function() { return [{ type: 'implicit', label: 'Access token (browser flow)' }, { type: 'authorization_code', label: 'Authorization code (server flow)' }, { type: 'client_credentials', label: 'Client credentials' }, { type: 'password', label: 'Password' }]; } }, // Currently available grant types. grantTypes: { type: Array }, // Tru whem the element has been initialized. _initialized: Boolean, // If true, the flow type selector will be forced to be opened _typeSelectorForceOpened: { type: Boolean, value: false }, /** * The element will automatically hide following fileds it the element has been initialized * with values for this fields (without user interaction): * * - autorization url * - token url * - scopes * * If all this values are set then the element will set `isAdvanced` attribute and set * `advancedOpened` to false * * Setting this property will prevent this behavior. */ noAuto: Boolean, /** * If set it will render autorization url, token url and scopes as advanced options * activated on user interaction. */ isAdvanced: Boolean, /** * If true then the advanced options are opened. */ advancedOpened: Boolean, /** * If set, the grant typr selector will be hidden from the UI. */ noGrantType: { type: Boolean, observer: '_noGrantTypeChanged' }, /** * List of query parameters to apply to authorization request. * This is allowed by the OAuth 2.0 spec as an extension of the * protocol. * This value is computed if the `ramlSettings` contains annotations * and one of it is `customSettings`. * See https://github.com/raml-org/raml-annotations for definition. */ authQueryParameters: { type: Array, readOnly: true }, /** * List of query parameters to apply to token request. * This is allowed by the OAuth 2.0 spec as an extension of the * protocol. * This value is computed if the `ramlSettings` contains annotations * and one of it is `customSettings`. * See https://github.com/raml-org/raml-annotations for definition. */ tokenQueryParameters: { type: Array, readOnly: true }, /** * List of headers to apply to token request. * This is allowed by the OAuth 2.0 spec as an extension of the * protocol. * This value is computed if the `ramlSettings` contains annotations * and one of it is `customSettings`. * See https://github.com/raml-org/raml-annotations for definition. */ tokenHeaders: { type: Array, readOnly: true }, /** * List of body parameters to apply to token request. * This is allowed by the OAuth 2.0 spec as an extension of the * protocol. * This value is computed if the `ramlSettings` contains annotations * and one of it is `customSettings`. * See https://github.com/raml-org/raml-annotations for definition. */ tokenBody: { type: Array, readOnly: true } }, get _queryModelOpts() { return { valueDelimiter: '=', decodeValues: true }; }, get _headersModelOpts() { return { valueDelimiter: ':' }; }, observers: [ // jscs:disable maximumLineLength '_settingsChanged(grantType, clientId, clientSecret, authUrl, accessTokenUrl, password, username, scopes.*, tokenValue, _initialized)', // jscs:enable maximumLineLength '_ramlSettingsChanged(ramlSettings)', '_updateStepperState(noStepper)' ], ready: function() { this._initialized = true; Polymer.RenderStatus.afterNextRender(this, function() { if (!this.grantTypes) { this._updateGrantTypes(); } this._autoHide(); }); }, _attachListeners: function(node) { this.listen(node, 'oauth2-error', '_oauth2ErrorHandler'); this.listen(node, 'oauth2-token-response', '_oauth2SccessHandler'); this.listen(node, 'request-header-changed', '_headerChangedHandler'); }, _detachListeners: function(node) { this.unlisten(node, 'oauth2-error', '_oauth2ErrorHandler'); this.unlisten(node, 'oauth2-token-response', '_oauth2SccessHandler'); this.unlisten(node, 'request-header-changed', '_headerChangedHandler'); }, /** * This function hides all non-crucial fields that has been pre-filled when element has been * initialize (values not provided by the user). Hidden fields will be available under * "advanced" options. * * To prevent this behavior set `no-auto` attribute on this element. */ _autoHide: function() { if (this.noAuto) { this.advancedOpened = true; return; } if (!!this.authUrl && !!this.accessTokenUrl && !!(this.scopes && this.scopes.length)) { this.isAdvanced = true; this.advancedOpened = false; } else { this.advancedOpened = true; } }, /** * Gets a map of current settings (user provided data). */ get settings() { return this._getSettings(); }, /** * Validate the form. * * @param {Boolean} passive If true then it will check validity of the form but will not * highlight the fields when invalid. Default to false. * @return {Boolean} `true` if valid, `false` otherwise. */ validate: function(passive) { return passive ? this.$.form.checkValidity() : this.$.form.validate(); }, _settingsChanged: function() { var validationResult = this.$.form.validate(); var settings = this._getSettings(); var detail = { settings: settings, type: 'oauth2', valid: validationResult }; this.fire('auth-settings-changed', detail); }, // Removes a value from the (paper-)input going up through path of the event. _clearField: function(e) { e = Polymer.dom(e); var path = e.path; var inputTarget; while ((inputTarget = path.shift())) { if (inputTarget.nodeName === 'INPUT' || inputTarget.nodeName === 'PAPER-INPUT') { break; } } if (!inputTarget) { return; } inputTarget.value = ''; }, // Checks if the HTML element should be visible in the UI for given properties. _isFieldDisabled: function() { var args = Array.from(arguments); var isCustom = args.splice(0, 1)[0]; if (isCustom) { return false; } var grantType = args.splice(0, 1)[0]; return args.indexOf(grantType) === -1; }, /** * Computes the `required` attribute on input fileld. * When using custom grant type all fields are not required. * * @param {Boolean} isCustomGrant * @return {Boolean} */ _isFieldRequired: function(isCustomGrant) { return !isCustomGrant; }, // Handler for "authorize" button click. Sends the `oauth2-token-requested` event. authorize: function() { var validationResult = this.$.form.validate(); if (!validationResult) { return; } var detail = this._getSettings(); this._lastState = this.generateState(); detail.state = this._lastState; this._authorizing = true; this.fire('oauth2-token-requested', detail); }, /** * Generates `state` parameter for the OAuth2 call. * * @return {String} Generated state string. */ generateState: function() { var text = ''; var possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; for (var i = 0; i < 6; i++) { text += possible.charAt(Math.floor(Math.random() * possible.length)); } return text; }, /** * Returns current configuration of the OAuth2. * * @return {Object} Current OAuth2 configuration. */ _getSettings: function() { var detail = { type: this.grantType, clientId: this.clientId, tokenValue: this.tokenValue || '', scopes: this.scopes, customData: { auth: { parameters: undefined }, token: { parameters: undefined, headers: undefined, body: undefined } } }; switch (this.grantType) { case 'implicit': // The browser flow. detail.authorizationUrl = this.authUrl; detail.redirectUrl = this.redirectUrl; this._computeAuthCustomData(detail); break; case 'authorization_code': // The server flow. detail.authorizationUrl = this.authUrl; detail.clientSecret = this.clientSecret; detail.accessTokenUrl = this.accessTokenUrl; detail.redirectUrl = this.redirectUrl; this._computeAuthCustomData(detail); this._computeTokenCustomData(detail); break; case 'client_credentials': // The server flow. detail.clientSecret = this.clientSecret; detail.accessTokenUrl = this.accessTokenUrl; this._computeTokenCustomData(detail); break; case 'password': // The server flow. detail.username = this.username; detail.password = this.password; detail.accessTokenUrl = this.accessTokenUrl; this._computeTokenCustomData(detail); break; default: // Custom grant type. detail.authorizationUrl = this.authUrl; detail.clientSecret = this.clientSecret; detail.accessTokenUrl = this.accessTokenUrl; detail.redirectUrl = this.redirectUrl; detail.username = this.username; detail.password = this.password; this._computeAuthCustomData(detail); this._computeTokenCustomData(detail); break; } return detail; }, /** * Adds `customData` property values that can be applied to the * authorization request. * * @param {Object} detail Token request detail object. The object is passed * by reference so no need for return value */ _computeAuthCustomData: function(detail) { if (this.authQueryParameters) { detail.customData.auth.parameters = this._computeCustomParameters(this.authQueryParameters); } }, /** * Adds `customData` property values that can be applied to the * token request. * * @param {Object} detail Token request detail object. The object is passed * by reference so no need for return value */ _computeTokenCustomData: function(detail) { if (this.tokenQueryParameters) { detail.customData.token.parameters = this._computeCustomParameters(this.tokenQueryParameters); } if (this.tokenHeaders) { detail.customData.token.headers = this._computeCustomParameters(this.tokenHeaders); } if (this.tokenBody) { detail.customData.token.body = this._computeCustomParameters(this.tokenBody); } }, /** * Computes list of parameter values from current model. * * This function ignores empty values if they are not required. * Required property are always included, even if the value is not set. * * @param {Array} params Model for form inputs. * @return {Array|undefined} Array of objects with `name` and `value` * properties or undefined if `params` is empty or no values are available. */ _computeCustomParameters: function(params) { if (!params || !params.length) { return; } var result = []; params.forEach(function(item) { if (!item.value && !item.required) { return; } result.push({ name: item.key, value: item.value || '' }); }); if (result.length === 0) { return; } return result; }, /** * Restores settings from stored value. * * @param {Object} settings Object returned by `_getSettings()` */ restore: function(settings) { this.grantType = settings.type; this.clientId = settings.clientId; this.tokenValue = settings.tokenValue; this.scopes = settings.scopes; switch (this.grantType) { case 'implicit': this.authUrl = settings.authorizationUrl; break; case 'authorization_code': this.authUrl = settings.authorizationUrl; this.clientSecret = settings.clientSecret; this.accessTokenUrl = settings.accessTokenUrl; break; case 'client_credentials': // The server flow. this.clientSecret = settings.clientSecret; this.accessTokenUrl = settings.accessTokenUrl; break; case 'password': // The server flow. this.username = settings.username; this.password = settings.password; this.accessTokenUrl = settings.accessTokenUrl; break; } }, // Handler for the OAuth token value change. Enables the method if not active. _tokenValueChanged: function(newValue) { this._authorizing = false; if (newValue) { this._setHasTokenValue(true); } else { this._setHasTokenValue(false); } }, _oauth2ErrorHandler: function(e) { var info = e.detail; // API console may not support state check (mey not return it back) if (typeof info.state !== 'undefined') { if (info.state !== this._lastState) { return; } } this._authorizing = false; this._lastState = undefined; var toast = this.$$('paper-toast'); toast.text = info.message; toast.opened = true; }, _oauth2SccessHandler: function(e) { var info = e.detail; // API console may not support state check (mey not return it back) if (typeof info.state !== 'undefined') { if (info.state !== this._lastState) { return; } } this._lastState = undefined; if (info.accessToken && info.accessToken !== this.tokenValue) { this.set('tokenValue', info.accessToken); this.fire('oauth2-token-ready', { token: info.accessToken }); } }, _ramlSettingsChanged: function(type) { if (!type || !type.settings) { return; } this._clearDocs(); this.preFill(type.settings); this._autoHide(); }, preFill: function(settings) { if (!settings) { throw new Error('The `settings` argument is not set.'); } var oauthAnnotation = this._extractOauthAnnotation(settings); var grants = settings.authorizationGrants; if (typeof grants === 'string') { grants = [grants]; } grants = this._applyAnnotationGranst(grants, oauthAnnotation); if (grants && grants instanceof Array && grants.length) { var index = grants.indexOf('code'); if (index !== -1) { grants[index] = 'authorization_code'; } this._updateGrantTypes(grants); } else { this._updateGrantTypes(); } if (settings.accessTokenUri) { this.accessTokenUrl = settings.accessTokenUri; } if (settings.authorizationUri) { this.authUrl = settings.authorizationUri; } if (settings.scopes) { this.scopes = settings.scopes; } this._setupAnotationParameters(oauthAnnotation); }, /** * Extracts `customSettings` annotation and validates it. * * @param {Object} settind Settings object from the security scheme. * @return {Object|undefined} OAuth settings annotation published in * `raml-org/oauth-annotations` repository. */ _extractOauthAnnotation: function(settings) { var result; if (!settings.annotations || !settings.annotations.length) { return result; } var annotation; for (var i = 0, len = settings.annotations.length; i < len; i++) { if (settings.annotations[i].name === 'customSettings') { annotation = settings.annotations[i]; break; } } if (!annotation || !annotation.structuredValue) { return result; } result = annotation.structuredValue; return result; }, /** * Applies `authorizationGrants` from OAuth2 settings annotation. * * @param {Array} gransts OAuth spec grants available for the endpoint * @param {?Object} annotation Read annotation. * @return {Array} List of granst to apply. */ _applyAnnotationGranst: function(gransts, annotation) { if (!annotation) { return gransts; } if (!gransts) { gransts = []; } var otherGransts = annotation.authorizationGrants; if (!otherGransts || !otherGransts.length) { return gransts; } if (typeof annotation.ignoreDefaultGrants !== 'undefined') { gransts = []; } gransts = gransts.concat(otherGransts); return gransts; }, /** * Sets up annotation supported variables to apply form view for: * - authorization query parameters * - authorization headers * - token query parameters * - token headers * - token body * * @param {Object} annotation Annotation applied to the OAuth settings */ _setupAnotationParameters: function(annotation) { if (this.authQueryParameters) { this._setAuthQueryParameters(undefined); } if (this.tokenQueryParameters) { this._setTokenQueryParameters(undefined); } if (this.tokenHeaders) { this._setTokenHeaders(undefined); } if (this.tokenBody) { this._setTokenBody(undefined); } if (!annotation) { return; } if (annotation.authorizationSettings) { if (annotation.authorizationSettings.queryParameters) { this._setupAuthRequestQueryParameters(annotation.authorizationSettings.queryParameters); }