diff --git a/src/core/components/auth/oauth2.jsx b/src/core/components/auth/oauth2.jsx index aef95ac7..6c9cfde9 100644 --- a/src/core/components/auth/oauth2.jsx +++ b/src/core/components/auth/oauth2.jsx @@ -122,11 +122,13 @@ export default class Oauth2 extends React.Component { const { isOAS3 } = specSelectors + let oidcUrl = isOAS3() ? schema.get("openIdConnectUrl") : null + // Auth type consts const IMPLICIT = "implicit" const PASSWORD = "password" - const ACCESS_CODE = isOAS3() ? "authorizationCode" : "accessCode" - const APPLICATION = isOAS3() ? "clientCredentials" : "application" + const ACCESS_CODE = isOAS3() ? (oidcUrl ? "authorization_code" : "authorizationCode") : "accessCode" + const APPLICATION = isOAS3() ? (oidcUrl ? "client_credentials" : "clientCredentials") : "application" let flow = schema.get("flow") let scopes = schema.get("allowedScopes") || schema.get("scopes") @@ -144,6 +146,7 @@ export default class Oauth2 extends React.Component { { isAuthorized &&
Authorized
} + { oidcUrl &&

OpenID Connect URL: { oidcUrl }

} { ( flow === IMPLICIT || flow === ACCESS_CODE ) &&

Authorization URL: { schema.get("authorizationUrl") }

} { ( flow === PASSWORD || flow === ACCESS_CODE || flow === APPLICATION ) &&

Token URL: { schema.get("tokenUrl") }

}

Flow: { schema.get("flow") }

diff --git a/src/core/oauth2-authorize.js b/src/core/oauth2-authorize.js index 03d62864..e8fcedd6 100644 --- a/src/core/oauth2-authorize.js +++ b/src/core/oauth2-authorize.js @@ -26,11 +26,13 @@ export default function authorize ( { auth, authActions, errActions, configs, au break case "clientCredentials": + case "client_credentials": // OAS3 authActions.authorizeApplication(auth) return case "authorizationCode": + case "authorization_code": // OAS3 query.push("response_type=code") break diff --git a/src/core/plugins/oas3/auth-extensions/wrap-selectors.js b/src/core/plugins/oas3/auth-extensions/wrap-selectors.js index 25fd7adb..847db871 100644 --- a/src/core/plugins/oas3/auth-extensions/wrap-selectors.js +++ b/src/core/plugins/oas3/auth-extensions/wrap-selectors.js @@ -8,10 +8,13 @@ import { isOAS3 as isOAS3Helper } from "../helpers" const state = state => state function onlyOAS3(selector) { - return (ori, system) => (state, ...args) => { + return (ori, system) => (...args) => { const spec = system.getSystem().specSelectors.specJson() if(isOAS3Helper(spec)) { - return selector(system, ...args) + // Pass the spec plugin state to Reselect to trigger on securityDefinitions update + let resolvedSchemes = system.getState().getIn(["spec", "resolvedSubtrees", + "components", "securitySchemes"]) + return selector(system, resolvedSchemes, ...args) } else { return ori(...args) } @@ -57,6 +60,32 @@ export const definitionsToAuthorize = onlyOAS3(createSelector( [defName]: definition })) } + if(type === "openIdConnect" && definition.get("openIdConnectData")) { + let oidcData = definition.get("openIdConnectData") + let grants = oidcData.get("grant_types_supported") || ["authorization_code", "implicit"] + grants.forEach((grant) => { + // Convert from OIDC list of scopes to the OAS-style map with empty descriptions + let translatedScopes = oidcData.get("scopes_supported") && + oidcData.get("scopes_supported").reduce((acc, cur) => acc.set(cur, ""), new Map()) + + let translatedDef = fromJS({ + flow: grant, + authorizationUrl: oidcData.get("authorization_endpoint"), + tokenUrl: oidcData.get("token_endpoint"), + scopes: translatedScopes, + type: "oauth2", + openIdConnectUrl: definition.get("openIdConnectUrl") + }) + + list = list.push(new Map({ + [defName]: translatedDef.filter((v) => { + // filter out unset values, sometimes `authorizationUrl` + // and `tokenUrl` come out as `undefined` in the data + return v !== undefined + }) + })) + }) + } }) return list diff --git a/src/core/plugins/spec/actions.js b/src/core/plugins/spec/actions.js index 1585795c..37934126 100644 --- a/src/core/plugins/spec/actions.js +++ b/src/core/plugins/spec/actions.js @@ -150,6 +150,7 @@ const debResolveSubtrees = debounce(async () => { errSelectors, fn: { resolveSubtree, + fetch, AST = {} }, specSelectors, @@ -206,6 +207,28 @@ const debResolveSubtrees = debounce(async () => { errActions.newThrownErrBatch(preparedErrors) } + if (spec && specSelectors.isOAS3() && path[0] === "components" && path[1] === "securitySchemes") { + // Resolve OIDC URLs if present + await Promise.all(Object.values(spec) + .filter((scheme) => scheme.type === "openIdConnect") + .map(async (oidcScheme) => { + const req = { + url: oidcScheme.openIdConnectUrl, + requestInterceptor: requestInterceptor, + responseInterceptor: responseInterceptor + } + try { + const res = await fetch(req) + if (res instanceof Error || res.status >= 400) { + console.error(res.statusText + " " + req.url) + } else { + oidcScheme.openIdConnectData = JSON.parse(res.text) + } + } catch (e) { + console.error(e) + } + })) + } set(resultMap, path, spec) set(specWithCurrentSubtrees, path, spec) diff --git a/test/unit/core/plugins/oas3/wrap-auth-selectors.js b/test/unit/core/plugins/oas3/wrap-auth-selectors.js index 578f42b2..a2203b94 100644 --- a/test/unit/core/plugins/oas3/wrap-auth-selectors.js +++ b/test/unit/core/plugins/oas3/wrap-auth-selectors.js @@ -1,5 +1,5 @@ -import { fromJS } from "immutable" +import { fromJS, Map } from "immutable" import { definitionsToAuthorize } from "corePlugins/oas3/auth-extensions/wrap-selectors" @@ -12,6 +12,7 @@ describe("oas3 plugin - auth extensions - wrapSelectors", function(){ // Given const system = { getSystem: () => system, + getState: () => new Map(), specSelectors: { specJson: () => fromJS({ openapi: "3.0.0" @@ -53,7 +54,39 @@ describe("oas3 plugin - auth extensions - wrapSelectors", function(){ } } } - } + }, + "oidc": { + "type": "openIdConnect", + "openIdConnectUrl": "https://accounts.google.com/.well-known/openid-configuration", + "openIdConnectData": { + "authorization_endpoint": "https://accounts.google.com/o/oauth2/v2/auth", + "token_endpoint": "https://oauth2.googleapis.com/token", + "scopes_supported": [ + "openid", + "email", + "profile" + ], + "grant_types_supported": [ + "authorization_code", + "refresh_token", + "urn:ietf:params:oauth:grant-type:device_code", + "urn:ietf:params:oauth:grant-type:jwt-bearer" + ] + } + }, + "oidcNoGrant": { + "type": "openIdConnect", + "openIdConnectUrl": "https://accounts.google.com/.well-known/openid-configuration", + "openIdConnectData": { + "authorization_endpoint": "https://accounts.google.com/o/oauth2/v2/auth", + "token_endpoint": "https://oauth2.googleapis.com/token", + "scopes_supported": [ + "openid", + "email", + "profile" + ] + }, + }, }) } } @@ -106,6 +139,96 @@ describe("oas3 plugin - auth extensions - wrapSelectors", function(){ type: "oauth2" } }, + { + oidc: { + flow: "authorization_code", + authorizationUrl: "https://accounts.google.com/o/oauth2/v2/auth", + tokenUrl: "https://oauth2.googleapis.com/token", + openIdConnectUrl: "https://accounts.google.com/.well-known/openid-configuration", + scopes: { + "openid": "", + "email": "", + "profile": "", + }, + type: "oauth2" + } + }, + { + oidc: { + flow: "refresh_token", + authorizationUrl: "https://accounts.google.com/o/oauth2/v2/auth", + tokenUrl: "https://oauth2.googleapis.com/token", + openIdConnectUrl: "https://accounts.google.com/.well-known/openid-configuration", + scopes: { + "openid": "", + "email": "", + "profile": "", + }, + type: "oauth2" + } + }, + { + oidc: { + flow: "urn:ietf:params:oauth:grant-type:device_code", + authorizationUrl: "https://accounts.google.com/o/oauth2/v2/auth", + tokenUrl: "https://oauth2.googleapis.com/token", + openIdConnectUrl: "https://accounts.google.com/.well-known/openid-configuration", + scopes: { + "openid": "", + "email": "", + "profile": "", + }, + type: "oauth2" + } + }, + { + oidc: { + flow: "urn:ietf:params:oauth:grant-type:jwt-bearer", + authorizationUrl: "https://accounts.google.com/o/oauth2/v2/auth", + tokenUrl: "https://oauth2.googleapis.com/token", + openIdConnectUrl: "https://accounts.google.com/.well-known/openid-configuration", + scopes: { + "openid": "", + "email": "", + "profile": "", + }, + type: "oauth2" + } + }, + { + // See https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata + // grant_types_supported + // OPTIONAL. JSON array containing a list of the OAuth 2.0 Grant Type values that + // this OP supports. Dynamic OpenID Providers MUST support the authorization_code + // and implicit Grant Type values and MAY support other Grant Types. If omitted, + // the default value is ["authorization_code", "implicit"]. + oidcNoGrant: { + flow: "authorization_code", + authorizationUrl: "https://accounts.google.com/o/oauth2/v2/auth", + tokenUrl: "https://oauth2.googleapis.com/token", + openIdConnectUrl: "https://accounts.google.com/.well-known/openid-configuration", + scopes: { + "openid": "", + "email": "", + "profile": "", + }, + type: "oauth2" + } + }, + { + oidcNoGrant: { + flow: "implicit", + authorizationUrl: "https://accounts.google.com/o/oauth2/v2/auth", + tokenUrl: "https://oauth2.googleapis.com/token", + openIdConnectUrl: "https://accounts.google.com/.well-known/openid-configuration", + scopes: { + "openid": "", + "email": "", + "profile": "", + }, + type: "oauth2" + } + }, ]) })