improve: OAuth2 UI and test suite (via #5066)

* create `features` folder

* add base oauth2 server

* continue implementing OAuth tests

* WIP

* add password flow tests

* modify Password flow credential types

* remove query string credential type

* add test case for Authorization flow

* add specific Authorization value for Password flow test

* WIP

* fix linter issues
This commit is contained in:
kyle
2018-12-07 20:54:29 +01:00
committed by GitHub
parent 91b1becc65
commit a5568f9e16
14 changed files with 504 additions and 55 deletions

View File

@@ -0,0 +1,50 @@
// from https://github.com/pedroetb/node-oauth2-server-example
var Http = require("http")
var path = require("path")
var express = require("express")
var bodyParser = require("body-parser")
var oauthserver = require("oauth2-server")
var cors = require("cors")
var app = express()
app.use(cors())
app.use(bodyParser.urlencoded({ extended: true }))
app.use(bodyParser.json())
app.oauth = oauthserver({
model: require("./model.js"),
grants: ["password", "client_credentials", "implicit"],
debug: true
})
app.all("/oauth/token", app.oauth.grant())
app.get("/swagger.yaml", function (req, res) {
res.sendFile(path.join(__dirname, "swagger.yaml"))
})
app.get("*", app.oauth.authorise(), function (req, res) {
res.send("Secret secrets are no fun, secret secrets hurt someone.")
})
app.use(app.oauth.errorHandler())
function startServer() {
var httpServer = Http.createServer(app)
httpServer.listen("3231")
return function stopServer() {
httpServer.close()
}
}
module.exports = startServer
if (require.main === module) {
// for debugging
startServer()
}

View File

@@ -0,0 +1,141 @@
// from https://github.com/pedroetb/node-oauth2-server-example
var config = {
clients: [{
clientId: "application",
clientSecret: "secret"
}],
confidentialClients: [{
clientId: "confidentialApplication",
clientSecret: "topSecret"
}],
tokens: [],
users: [{
id: "123",
username: "swagger",
password: "password"
}]
}
/**
* Dump the memory storage content (for debug).
*/
var dump = function () {
console.log("clients", config.clients)
console.log("confidentialClients", config.confidentialClients)
console.log("tokens", config.tokens)
console.log("users", config.users)
}
/*
* Methods used by all grant types.
*/
var getAccessToken = function (bearerToken, callback) {
var tokens = config.tokens.filter(function (token) {
return token.accessToken === bearerToken
})
return callback(false, tokens[0])
}
var getClient = function (clientId, clientSecret, callback) {
var clients = config.clients.filter(function (client) {
return client.clientId === clientId && client.clientSecret === clientSecret
})
var confidentialClients = config.confidentialClients.filter(function (client) {
return client.clientId === clientId && client.clientSecret === clientSecret
})
callback(false, clients[0] || confidentialClients[0])
}
var grantTypeAllowed = function (clientId, grantType, callback) {
var clientsSource,
clients = []
if (grantType === "password") {
clientsSource = config.clients
} else if (grantType === "client_credentials") {
clientsSource = config.confidentialClients
}
if (clientsSource) {
clients = clientsSource.filter(function (client) {
return client.clientId === clientId
})
}
callback(false, clients.length)
}
var saveAccessToken = function (accessToken, clientId, expires, user, callback) {
config.tokens.push({
accessToken: accessToken,
expires: expires,
clientId: clientId,
user: user
})
callback(false)
}
/*
* Method used only by password grant type.
*/
var getUser = function (username, password, callback) {
var users = config.users.filter(function (user) {
return user.username === username && user.password === password
})
callback(false, users[0])
}
/*
* Method used only by client_credentials grant type.
*/
var getUserFromClient = function (clientId, clientSecret, callback) {
var clients = config.confidentialClients.filter(function (client) {
return client.clientId === clientId && client.clientSecret === clientSecret
})
var user
if (clients.length) {
user = {
username: clientId
}
}
callback(false, user)
}
/**
* Export model definition object.
*/
module.exports = {
getAccessToken: getAccessToken,
getClient: getClient,
grantTypeAllowed: grantTypeAllowed,
saveAccessToken: saveAccessToken,
getUser: getUser,
getUserFromClient: getUserFromClient
}

View File

@@ -0,0 +1,36 @@
swagger: "2.0"
host: localhost:3231
paths:
/password:
get:
summary: OAuth2 Password
security:
- oauthPassword: []
responses:
200:
description: OK
schema:
type: string
/application:
get:
summary: OAuth2 Application
security:
- oauthApplication: []
responses:
200:
description: OK
schema:
type: string
securityDefinitions:
oauthPassword:
type: oauth2
flow: password
tokenUrl: /oauth/token
oauthApplication:
type: oauth2
flow: application
tokenUrl: /oauth/token
oauthImplicit:
type: oauth2
flow: implicit
authorizationUrl: /oauth/token

View File

@@ -1,3 +1,4 @@
const startOAuthServer = require("../helpers/oauth2-server")
// ***********************************************************
// This example plugins/index.js can be used to load plugins
//
@@ -12,6 +13,7 @@
// the project's config changing)
module.exports = (on, config) => {
startOAuthServer()
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
}

View File

@@ -18,3 +18,10 @@ import "./commands"
// Alternatively you can use CommonJS syntax:
// require('./commands')
// Remove fetch, so Cypress can intercept XHRs
// see https://github.com/cypress-io/cypress/issues/95
Cypress.on("window:before:load", win => {
win.fetch = null
})

View File

@@ -0,0 +1,55 @@
describe("OAuth2 Application flow", function() {
beforeEach(() => {
cy.server()
cy.route({
url: "**/oauth/*",
method: "POST"
}).as("tokenRequest")
})
it("should make an application flow Authorization header request", () => {
cy
.visit("/?url=http://localhost:3231/swagger.yaml")
.get(".btn.authorize")
.click()
.get("div.modal-ux-content > div:nth-child(2)").within(() => {
cy.get("#client_id")
.clear()
.type("confidentialApplication")
.get("#client_secret")
.clear()
.type("topSecret")
.get("button.btn.modal-btn.auth.authorize.button")
.click()
})
cy.get("button.close-modal")
.click()
.get("#operations-default-get_application")
.click()
.get(".btn.try-out__btn")
.click()
.get(".btn.execute")
.click()
cy.get("@tokenRequest")
.its("request")
.its("body")
.should("equal", "grant_type=client_credentials")
cy.get("@tokenRequest")
.its("request")
.its("headers")
.its("authorization")
.should("equal", "Basic Y29uZmlkZW50aWFsQXBwbGljYXRpb246dG9wU2VjcmV0")
.get(".live-responses-table .response-col_status")
.contains("200")
})
})

View File

@@ -0,0 +1,122 @@
describe("OAuth2 Password flow", function() {
beforeEach(() => {
cy.server()
cy.route({
url: "**/oauth/*",
method: "POST"
}).as("tokenRequest")
})
it("should make a password flow Authorization header request", () => {
cy
.visit("/?url=http://localhost:3231/swagger.yaml")
.get(".btn.authorize")
.click()
.get("#oauth_username")
.type("swagger")
.get("#oauth_password")
.type("password")
.get("#password_type")
.select("basic")
.get("#client_id")
.clear()
.type("application")
.get("#client_secret")
.clear()
.type("secret")
.get("div.modal-ux-content > div:nth-child(1) > div > div:nth-child(2) > div > div.auth-btn-wrapper > button.btn.modal-btn.auth.authorize.button")
.click()
.get("button.close-modal")
.click()
.get("#operations-default-get_password")
.click()
.get(".btn.try-out__btn")
.click()
.get(".btn.execute")
.click()
cy.get("@tokenRequest")
.its("request")
.its("body")
.should("include", "grant_type=password")
.should("include", "username=swagger")
.should("include", "password=password")
.should("not.include", "client_id")
.should("not.include", "client_secret")
cy.get("@tokenRequest")
.its("request")
.its("headers")
.its("authorization")
.should("equal", "Basic YXBwbGljYXRpb246c2VjcmV0")
.get(".live-responses-table .response-col_status")
.contains("200")
})
it("should make a Password flow request-body request", () => {
cy
.visit("/?url=http://localhost:3231/swagger.yaml")
.get(".btn.authorize")
.click()
.get("#oauth_username")
.type("swagger")
.get("#oauth_password")
.type("password")
.get("#password_type")
.select("request-body")
.get("#client_id")
.clear()
.type("application")
.get("#client_secret")
.clear()
.type("secret")
.get("div.modal-ux-content > div:nth-child(1) > div > div:nth-child(2) > div > div.auth-btn-wrapper > button.btn.modal-btn.auth.authorize.button")
.click()
.get("button.close-modal")
.click()
.get("#operations-default-get_password")
.click()
.get(".btn.try-out__btn")
.click()
.get(".btn.execute")
.click()
cy.get("@tokenRequest")
.its("request")
.its("body")
.should("include", "grant_type=password")
.should("include", "username=swagger")
.should("include", "password=password")
.should("include", "client_id=application")
.should("include", "client_secret=secret")
cy.get("@tokenRequest")
.its("request")
.its("headers")
.should("not.have.property", "authorization")
.get(".live-responses-table .response-col_status")
.contains("200")
})
})