commit
42c051d55a
3 changed files with 264 additions and 0 deletions
@ -0,0 +1,21 @@ |
|||||
|
The MIT License (MIT) |
||||
|
|
||||
|
Copyright (c) 2016 Nicolas MASSE |
||||
|
|
||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
|
of this software and associated documentation files (the "Software"), to deal |
||||
|
in the Software without restriction, including without limitation the rights |
||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
|
copies of the Software, and to permit persons to whom the Software is |
||||
|
furnished to do so, subject to the following conditions: |
||||
|
|
||||
|
The above copyright notice and this permission notice shall be included in all |
||||
|
copies or substantial portions of the Software. |
||||
|
|
||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||
|
SOFTWARE. |
||||
@ -0,0 +1,16 @@ |
|||||
|
{ |
||||
|
"name": "3scale-sample-webhook", |
||||
|
"version": "0.0.1", |
||||
|
"description": "A sample app that handles 3scale webhooks", |
||||
|
"main": "server.js", |
||||
|
"scripts": {}, |
||||
|
"author": "Nicolas MASSE", |
||||
|
"repository": "", |
||||
|
"license": "MIT", |
||||
|
"dependencies": { |
||||
|
"express": "latest", |
||||
|
"express-xml-bodyparser": "^0.3.0", |
||||
|
"request": "^2.81.0", |
||||
|
"underscore": "^1.8.3" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,227 @@ |
|||||
|
// Dependencies
|
||||
|
var express = require("express"); |
||||
|
var _ = require("underscore"); |
||||
|
var util = require('util'); |
||||
|
var xmlparser = require('express-xml-bodyparser'); |
||||
|
var req = require('request'); |
||||
|
|
||||
|
// ExpressJS Setup
|
||||
|
var app = express(); |
||||
|
var router = express.Router(); |
||||
|
var port = 8080; |
||||
|
|
||||
|
var my_url = "/webhook"; |
||||
|
var shared_secret = process.env.SHARED_SECRET; |
||||
|
if (shared_secret == null || shared_secret == "") { |
||||
|
console.log("WARNING: Authentication is DISABLED !"); |
||||
|
console.log("WARNING: Please add an environment variable named 'SHARED_SECRET' to enable authentication"); |
||||
|
} else { |
||||
|
my_url += util.format("?shared_secret=%s", encodeURIComponent(shared_secret)); |
||||
|
} |
||||
|
|
||||
|
var failed = false; |
||||
|
var sso = {}; |
||||
|
_.each(['SSO_REALM', 'SSO_HOSTNAME', 'SSO_CLIENT_ID', 'SSO_SERVICE_USERNAME', 'SSO_SERVICE_PASSWORD'], (item) => { |
||||
|
if ((item in process.env) && (process.env[item] != null)) { |
||||
|
sso[item] = process.env[item]; |
||||
|
} else { |
||||
|
console.log("ERROR: Environment variable '%s' is missing or empty.", item); |
||||
|
failed = true; |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
if (failed) { |
||||
|
console.log("Exiting !") |
||||
|
process.exit(1) |
||||
|
} |
||||
|
|
||||
|
var webhooks_handlers = { |
||||
|
application: handle_application |
||||
|
}; |
||||
|
|
||||
|
// Log every request
|
||||
|
router.use(function (req,res,next) { |
||||
|
next(); |
||||
|
console.log("%s %s => %d", req.method, req.originalUrl, res.statusCode); |
||||
|
}); |
||||
|
|
||||
|
// Any GET on / ends up with a nice documentation as JSON
|
||||
|
router.get("/",function(req,res){ |
||||
|
var response = { |
||||
|
name: "3scale Sample Webhook", |
||||
|
description: "A sample project that handles 3scale webhooks", |
||||
|
endpoints: [ |
||||
|
{ |
||||
|
"url": "/webhook", |
||||
|
"verbs": [ "GET", "POST" ] |
||||
|
} |
||||
|
], |
||||
|
documentation: { |
||||
|
"GitHub": "TODO" |
||||
|
} |
||||
|
}; |
||||
|
success(res, 200, response); |
||||
|
}); |
||||
|
|
||||
|
// Ping Webhook
|
||||
|
router.get("/webhook",function(req,res){ |
||||
|
var response = { pong: "webhook" }; |
||||
|
success(res, 200, response); |
||||
|
}); |
||||
|
|
||||
|
// Handle Webhook
|
||||
|
router.post("/webhook",function(req,res){ |
||||
|
var payload = req.body; |
||||
|
if (payload == null) { |
||||
|
return error(res, 400, "No body sent !"); |
||||
|
} |
||||
|
|
||||
|
var event = payload.event; |
||||
|
if (event == null) { |
||||
|
return error(res, 400, "No event found in payload !"); |
||||
|
} |
||||
|
|
||||
|
var action = event.action; |
||||
|
var type = event.type; |
||||
|
var obj = event.object[type]; |
||||
|
if (obj == null) { |
||||
|
return error(res, 400, "No object found in payload !"); |
||||
|
} |
||||
|
|
||||
|
if (type in webhooks_handlers) { |
||||
|
return webhooks_handlers[type](res, action, type, obj); |
||||
|
} else { |
||||
|
error(res, 412, util.format("No handlers to handle '%s'", type)); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
function handle_application(res, action, type, app) { |
||||
|
console.log("action = %s, type = %s", action, type); |
||||
|
console.log(obj); |
||||
|
|
||||
|
var client = { |
||||
|
clientId: app.application_id, |
||||
|
clientAuthenticatorType: "client-secret", |
||||
|
secret: app.keys.key, |
||||
|
redirectUris: [ app.redirect_url ], |
||||
|
publicClient: false, |
||||
|
name: app.name, |
||||
|
description: app.description |
||||
|
}; |
||||
|
|
||||
|
authenticate_to_sso(res, (access_token) => { |
||||
|
get_sso_client(res, client.client_id, access_token, (sso_client) => { |
||||
|
if (sso_client == null) { |
||||
|
create_sso_client(res, access_token, client, (response) => { |
||||
|
success(res, 200, "TODO"); |
||||
|
}); |
||||
|
} else { |
||||
|
update_sso_client(res, access_token, client, sso_client.id, (response) => { |
||||
|
success(res, 200, "TODO"); |
||||
|
}); |
||||
|
} |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
//
|
||||
|
// Please find below the plumbing code
|
||||
|
//
|
||||
|
|
||||
|
// Register the XML Parser for POST requests
|
||||
|
app.use(xmlparser({explicitArray: false})); |
||||
|
|
||||
|
// Register the router
|
||||
|
app.use("/",router); |
||||
|
|
||||
|
// 404 Handler (Not Found)
|
||||
|
app.use("*",function(req,res){ |
||||
|
error(res, 404, "Not found"); |
||||
|
}); |
||||
|
|
||||
|
// Start the HTTP Server
|
||||
|
app.listen(port,function(){ |
||||
|
console.log("Webhook server listening at port %d", port); |
||||
|
console.log("Please use url 'https://<your_openshift_route>%s' in the Webhooks configuration of 3scale.", my_url); |
||||
|
}); |
||||
|
|
||||
|
function error(res, code, message) { |
||||
|
var response = { |
||||
|
status: code, |
||||
|
message: message |
||||
|
}; |
||||
|
return res.status(code) |
||||
|
.type("application/json") |
||||
|
.send(JSON.stringify(response)); |
||||
|
} |
||||
|
|
||||
|
function success(res, code, response) { |
||||
|
return res.status(code) |
||||
|
.type("application/json") |
||||
|
.send(JSON.stringify(response)); |
||||
|
} |
||||
|
|
||||
|
function get_sso_client(res, client_id, access_token, next) { |
||||
|
req.get(util.format("https://%s/auth/admin/realms/%s/clients", sso.SSO_HOSTNAME, sso.SSO_REALM), |
||||
|
{ |
||||
|
headers: { |
||||
|
"Authorization": "Bearer " + access_token |
||||
|
}, |
||||
|
qs: { |
||||
|
clientId: client_id |
||||
|
} |
||||
|
}).on('response', (response) => { |
||||
|
var json_response = JSON.parse(response.body); |
||||
|
var sso_client = null; |
||||
|
if (json_response.length > 0) { |
||||
|
sso_client = json_response[0]; |
||||
|
} |
||||
|
next(sso_client); |
||||
|
}).on('error', (err) => { |
||||
|
return error(res, 500, err); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function create_sso_client(res, access_token, client, next) { |
||||
|
req.post(util.format("https://%s/auth/admin/realms/%s/clients", sso.SSO_HOSTNAME, sso.SSO_REALM), { |
||||
|
headers: { |
||||
|
"Authorization": "Bearer " + access_token |
||||
|
}, |
||||
|
json: client |
||||
|
}).on('response', (response) => { |
||||
|
var client = JSON.parse(response.body); |
||||
|
next(client); |
||||
|
}).on('error', (err) => { |
||||
|
return error(res, 500, err); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function update_sso_client(res, access_token, client, id, next) { |
||||
|
req.put(util.format("https://%s/auth/admin/realms/%s/clients/%d", sso.SSO_HOSTNAME, sso.SSO_REALM, id), { |
||||
|
headers: { |
||||
|
"Authorization": "Bearer " + access_token |
||||
|
}, |
||||
|
json: client |
||||
|
}).on('response', (response) => { |
||||
|
var client = JSON.parse(response.body); |
||||
|
next(client); |
||||
|
}).on('error', (err) => { |
||||
|
return error(res, 500, err); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function authenticate_to_sso(res, next) { |
||||
|
req.post(util.format("https://%s/auth/realms/%s/protocol/openid-connect/token", sso.SSO_HOSTNAME, sso.SSO_REALM), { |
||||
|
form: { |
||||
|
grant_type: "password", |
||||
|
client_id: sso.SSO_CLIENT_ID, |
||||
|
username: sso.SSO_SERVICE_USERNAME, |
||||
|
password: sso.SSO_SERVICE_PASSWORD |
||||
|
}, |
||||
|
}).on('response', (response) => { |
||||
|
var json_response = JSON.parse(response.body); |
||||
|
next(json_response.access_token); |
||||
|
}).on('error', (err) => { |
||||
|
return error(res, 500, err); |
||||
|
}); |
||||
|
} |
||||
Loading…
Reference in new issue