Skip to content

Commit

Permalink
feat(oauth2): authActions.authPopup plugin accessible wrapper (#7699)
Browse files Browse the repository at this point in the history
* enables win.open to be extensible by plugins

Co-authored-by: Tim Lai <[email protected]>
fkowal and tim-lai authored Mar 10, 2022

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
1 parent a5aca55 commit 8f63462
Showing 4 changed files with 65 additions and 50 deletions.
7 changes: 2 additions & 5 deletions src/core/oauth2-authorize.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import parseUrl from "url-parse"
import win from "core/window"
import Im from "immutable"
import { btoa, sanitizeUrl, generateCodeVerifier, createCodeChallenge } from "core/utils"

@@ -123,13 +122,11 @@ export default function authorize ( { auth, authActions, errActions, configs, au
callback = authActions.authorizeAccessCodeWithFormParams
}

win.swaggerUIRedirectOauth2 = {
authActions.authPopup(url, {
auth: auth,
state: state,
redirectUrl: redirectUrl,
callback: callback,
errCb: errActions.newAuthErr
}

win.open(url)
})
}
12 changes: 9 additions & 3 deletions src/core/plugins/auth/actions.js
Original file line number Diff line number Diff line change
@@ -29,7 +29,7 @@ export function authorize(payload) {

export const authorizeWithPersistOption = (payload) => ( { authActions } ) => {
authActions.authorize(payload)
authActions.persistAuthorizationIfNeeded()
authActions.persistAuthorizationIfNeeded()
}

export function logout(payload) {
@@ -41,7 +41,7 @@ export function logout(payload) {

export const logoutWithPersistOption = (payload) => ( { authActions } ) => {
authActions.logout(payload)
authActions.persistAuthorizationIfNeeded()
authActions.persistAuthorizationIfNeeded()
}

export const preAuthorizeImplicit = (payload) => ( { authActions, errActions } ) => {
@@ -85,7 +85,7 @@ export function authorizeOauth2(payload) {

export const authorizeOauth2WithPersistOption = (payload) => ( { authActions } ) => {
authActions.authorizeOauth2(payload)
authActions.persistAuthorizationIfNeeded()
authActions.persistAuthorizationIfNeeded()
}

export const authorizePassword = ( auth ) => ( { authActions } ) => {
@@ -279,3 +279,9 @@ export const persistAuthorizationIfNeeded = () => ( { authSelectors, getConfigs
localStorage.setItem("authorized", JSON.stringify(authorized.toJS()))
}
}

export const authPopup = (url, swaggerUIRedirectOauth2) => ( ) => {
win.swaggerUIRedirectOauth2 = swaggerUIRedirectOauth2

win.open(url)
}
79 changes: 38 additions & 41 deletions test/unit/core/oauth2-authorize.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@

import win from "core/window"
import Im from "immutable"
import oauth2Authorize from "core/oauth2-authorize"
import * as utils from "core/utils"
@@ -13,75 +12,76 @@ describe("oauth2", () => {

let authConfig = {
auth: { schema: { get: (key)=> mockSchema[key] }, scopes: ["scope1", "scope2"] },
authActions: {},
authActions: {
authPopup: jest.fn()
},
errActions: {},
configs: { oauth2RedirectUrl: "" },
authConfigs: {}
}

let authConfig2 = {
auth: { schema: { get: (key)=> mockSchema[key] }, scopes: Im.List(["scope2","scope3"]) },
authActions: {},
authActions: {
authPopup: jest.fn()
},
errActions: {},
configs: { oauth2RedirectUrl: "" },
authConfigs: {}
}

let authConfig3 = {
auth: { schema: { get: (key)=> mockSchema[key] }, scopes: Im.List(["scope4","scope5"]) },
authActions: {},
authActions: {
authPopup: jest.fn()
},
errActions: {},
configs: { oauth2RedirectUrl: "" },
authConfigs: {}
}

beforeEach(() => {
win.open = jest.fn()
})

describe("authorize redirect", () => {
it("should build authorize url", () => {
const windowOpenSpy = jest.spyOn(win, "open")
oauth2Authorize(authConfig)
expect(windowOpenSpy.mock.calls.length).toEqual(1)
expect(windowOpenSpy.mock.calls[0][0]).toMatch("https://testAuthorizationUrl?response_type=code&redirect_uri=&scope=scope1%20scope2&state=")
expect(authConfig.authActions.authPopup.mock.calls.length).toEqual(1)
expect(authConfig.authActions.authPopup.mock.calls[0][0]).toMatch("https://testAuthorizationUrl?response_type=code&redirect_uri=&scope=scope1%20scope2&state=")

windowOpenSpy.mockReset()
authConfig.authActions.authPopup.mockReset()
})

it("should build authorize url relative", function () {
const windowOpenSpy = jest.spyOn(win, "open")
let relativeMockSchema = {
flow: "accessCode",
authorizationUrl: "/testAuthorizationUrl"
}
let relativeAuthConfig = {
auth: { schema: { get: (key) => relativeMockSchema[key] } },
authActions: {},
authActions: {
authPopup: jest.fn()
},
errActions: {},
configs: { oauth2RedirectUrl: "" },
authConfigs: {},
currentServer: "https://currentserver"
}
oauth2Authorize(relativeAuthConfig)
expect(windowOpenSpy.mock.calls.length).toEqual(1)
expect(windowOpenSpy.mock.calls[0][0]).toMatch("https://currentserver/testAuthorizationUrl?response_type=code&redirect_uri=&state=")
expect(relativeAuthConfig.authActions.authPopup.mock.calls.length).toEqual(1)
expect(relativeAuthConfig.authActions.authPopup.mock.calls[0][0]).toMatch("https://currentserver/testAuthorizationUrl?response_type=code&redirect_uri=&state=")

windowOpenSpy.mockReset()
relativeAuthConfig.authActions.authPopup.mockReset()
})

it("should append query parameters to authorizeUrl with query parameters", () => {
const windowOpenSpy = jest.spyOn(win, "open")
mockSchema.authorizationUrl = "https://testAuthorizationUrl?param=1"
oauth2Authorize(authConfig)
expect(windowOpenSpy.mock.calls.length).toEqual(1)
expect(windowOpenSpy.mock.calls[0][0]).toMatch("https://testAuthorizationUrl?param=1&response_type=code&redirect_uri=&scope=scope1%20scope2&state=")

windowOpenSpy.mockReset()
expect(authConfig.authActions.authPopup.mock.calls.length).toEqual(1)
expect(authConfig.authActions.authPopup.mock.calls[0][0]).toMatch("https://testAuthorizationUrl?param=1&response_type=code&redirect_uri=&scope=scope1%20scope2&state=")

authConfig.authActions.authPopup.mockReset()
})

it("should send code_challenge when using authorizationCode flow with usePkceWithAuthorizationCodeGrant enabled", () => {
const windowOpenSpy = jest.spyOn(win, "open")
mockSchema.flow = "authorizationCode"

const expectedCodeVerifier = "mock_code_verifier"
@@ -93,9 +93,9 @@ describe("oauth2", () => {
authConfig.authConfigs.usePkceWithAuthorizationCodeGrant = true

oauth2Authorize(authConfig)
expect(win.open.mock.calls.length).toEqual(1)
expect(authConfig.authActions.authPopup.mock.calls.length).toEqual(1)

const actualUrl = new URLSearchParams(win.open.mock.calls[0][0])
const actualUrl = new URLSearchParams(authConfig.authActions.authPopup.mock.calls[0][0])
expect(actualUrl.get("code_challenge")).toBe(expectedCodeChallenge)
expect(actualUrl.get("code_challenge_method")).toBe("S256")

@@ -107,14 +107,13 @@ describe("oauth2", () => {
expect(authConfig.auth.codeVerifier).toBe(expectedCodeVerifier)

// Restore spies
windowOpenSpy.mockReset()
authConfig.authActions.authPopup.mockReset()
generateCodeVerifierSpy.mockReset()
createCodeChallengeSpy.mockReset()
})


it("should send code_challenge when using accessCode flow with usePkceWithAuthorizationCodeGrant enabled", () => {
const windowOpenSpy = jest.spyOn(win, "open")
mockSchema.flow = "accessCode"

const expectedCodeVerifier = "mock_code_verifier"
@@ -127,9 +126,10 @@ describe("oauth2", () => {
authConfig.authConfigs.usePkceWithAuthorizationCodeGrant = true

oauth2Authorize(authConfig)
expect(win.open.mock.calls.length).toEqual(1)

const actualUrl = new URLSearchParams(win.open.mock.calls[0][0])
expect(authConfig.authActions.authPopup.mock.calls.length).toEqual(1)

const actualUrl = new URLSearchParams(authConfig.authActions.authPopup.mock.calls[0][0])
expect(actualUrl.get("code_challenge")).toBe(expectedCodeChallenge)
expect(actualUrl.get("code_challenge_method")).toBe("S256")

@@ -141,13 +141,12 @@ describe("oauth2", () => {
expect(authConfig.auth.codeVerifier).toBe(expectedCodeVerifier)

// Restore spies
windowOpenSpy.mockReset()
authConfig.authActions.authPopup.mockReset()
generateCodeVerifierSpy.mockReset()
createCodeChallengeSpy.mockReset()
})

it("should send code_challenge when using authorization_code flow with usePkceWithAuthorizationCodeGrant enabled", () => {
const windowOpenSpy = jest.spyOn(win, "open")
mockSchema.flow = "authorization_code"

const expectedCodeVerifier = "mock_code_verifier"
@@ -159,9 +158,9 @@ describe("oauth2", () => {
authConfig.authConfigs.usePkceWithAuthorizationCodeGrant = true

oauth2Authorize(authConfig)
expect(win.open.mock.calls.length).toEqual(1)
expect(authConfig.authActions.authPopup.mock.calls.length).toEqual(1)

const actualUrl = new URLSearchParams(win.open.mock.calls[0][0])
const actualUrl = new URLSearchParams(authConfig.authActions.authPopup.mock.calls[0][0])
expect(actualUrl.get("code_challenge")).toBe(expectedCodeChallenge)
expect(actualUrl.get("code_challenge_method")).toBe("S256")

@@ -173,32 +172,30 @@ describe("oauth2", () => {
expect(authConfig.auth.codeVerifier).toBe(expectedCodeVerifier)

// Restore spies
windowOpenSpy.mockReset()
authConfig.authActions.authPopup.mockReset()
generateCodeVerifierSpy.mockReset()
createCodeChallengeSpy.mockReset()
})

it("should add list of scopes to authorizeUrl", () => {
const windowOpenSpy = jest.spyOn(win, "open")
mockSchema.authorizationUrl = "https://testAuthorizationUrl?param=1"

oauth2Authorize(authConfig2)
expect(windowOpenSpy.mock.calls.length).toEqual(1)
expect(windowOpenSpy.mock.calls[0][0]).toMatch("https://testAuthorizationUrl?param=1&response_type=code&redirect_uri=&scope=scope2%20scope3&state=")
expect(authConfig2.authActions.authPopup.mock.calls.length).toEqual(1)
expect(authConfig2.authActions.authPopup.mock.calls[0][0]).toMatch("https://testAuthorizationUrl?param=1&response_type=code&redirect_uri=&scope=scope2%20scope3&state=")

windowOpenSpy.mockReset()
authConfig2.authActions.authPopup.mockReset()
})

it("should build authorize url for authorization_code flow", () => {
const windowOpenSpy = jest.spyOn(win, "open")
mockSchema.authorizationUrl = "https://testAuthorizationUrl"
mockSchema.flow = "authorization_code"

oauth2Authorize(authConfig3)
expect(windowOpenSpy.mock.calls.length).toEqual(1)
expect(windowOpenSpy.mock.calls[0][0]).toMatch("https://testAuthorizationUrl?response_type=code&redirect_uri=&scope=scope4%20scope5&state=")
expect(authConfig3.authActions.authPopup.mock.calls.length).toEqual(1)
expect(authConfig3.authActions.authPopup.mock.calls[0][0]).toMatch("https://testAuthorizationUrl?response_type=code&redirect_uri=&scope=scope4%20scope5&state=")

windowOpenSpy.mockReset()
authConfig3.authActions.authPopup.mockReset()
})
})
})
17 changes: 16 additions & 1 deletion test/unit/core/plugins/auth/actions.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Map } from "immutable"
import win from "core/window"
import {
authorizeRequest,
authorizeAccessCodeWithFormParams,
@@ -7,7 +8,7 @@ import {
logoutWithPersistOption,
persistAuthorizationIfNeeded
} from "corePlugins/auth/actions"
import { authorizeAccessCodeWithBasicAuthentication } from "../../../../../src/core/plugins/auth/actions"
import {authorizeAccessCodeWithBasicAuthentication, authPopup} from "../../../../../src/core/plugins/auth/actions"

describe("auth plugin - actions", () => {

@@ -327,5 +328,19 @@ describe("auth plugin - actions", () => {
})
})

describe("authPopup", () => {
beforeEach(() => {
win.open = jest.fn()
})
it("should call win.open with url", () => {
const windowOpenSpy = jest.spyOn(win, "open")

authPopup("http://swagger.ui", {})()

expect(windowOpenSpy.mock.calls.length).toEqual(1)
windowOpenSpy.mockReset()
})
})

})
})

0 comments on commit 8f63462

Please sign in to comment.