├── .gitattributes ├── .gitignore ├── AccessControlTestModule.js ├── ConnectingCircles-01.png ├── QlikLogo-RGB.png ├── README.md ├── SelectUser.htm ├── config.js ├── package.json ├── qlikview-sans.svg └── users.png /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #Node modules 2 | node_modules/ 3 | 4 | #Certificates files 5 | *.pfx 6 | *.cer 7 | 8 | # Windows image file caches 9 | Thumbs.db 10 | ehthumbs.db 11 | 12 | # Folder config file 13 | Desktop.ini 14 | 15 | # Recycle Bin used on file shares 16 | $RECYCLE.BIN/ 17 | 18 | # Windows Installer files 19 | *.cab 20 | *.msi 21 | *.msm 22 | *.msp 23 | 24 | # ========================= 25 | # Operating System Files 26 | # ========================= 27 | 28 | # OSX 29 | # ========================= 30 | 31 | .DS_Store 32 | .AppleDouble 33 | .LSOverride 34 | 35 | # Icon must end with two \r 36 | Icon 37 | 38 | # Thumbnails 39 | ._* 40 | 41 | # Files that might appear on external disk 42 | .Spotlight-V100 43 | .Trashes 44 | 45 | # Directories potentially created on remote AFP share 46 | .AppleDB 47 | .AppleDesktop 48 | Network Trash Folder 49 | Temporary Items 50 | .apdisk 51 | -------------------------------------------------------------------------------- /AccessControlTestModule.js: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================================ 3 | File: AccessControlTestModule.js 4 | Developer: Fredrik Lautrup 5 | Created Date: Sometime in 2014 6 | 7 | Description: 8 | The AccessControlTestModule uses node.js and express.js to create a lightweight web 9 | server for testing the ticketing authentication method in Qlik Sense Enterprise Server. 10 | 11 | WARNING!: 12 | This code is intended for testing and demonstration purposes only. It is not meant for 13 | production environments. In addition, the code is not supported by Qlik. 14 | 15 | Change Log 16 | Developer Change Description Modify Date 17 | -------------------------------------------------------------------------------------------- 18 | Fredrik Lautrup Initial Release circa Q4 2014 19 | Jeffrey Goldberg Updated for Expressjs v4.x 01-June-2015 20 | Fredrik Lautrup Added external config file 03-November-2015 21 | Steve Newman Updated Logout method and iframe support 07-January-2016 22 | 23 | -------------------------------------------------------------------------------------------- 24 | 25 | 26 | ============================================================================================ 27 | */ 28 | 29 | var config = require('./config'); 30 | var https = require('https'); 31 | var http = require('http'); 32 | var express=require('express'); 33 | var fs = require('fs'); 34 | var url= require('url'); 35 | 36 | var session = require('express-session'); 37 | var bodyParser = require('body-parser'); 38 | var cookieParser = require('cookie-parser'); 39 | var querystring = require("querystring"); 40 | 41 | var app = express(); 42 | //set the port for the listener here 43 | app.set('port', config.port); 44 | 45 | 46 | //new Expressjs 4.x notation for configuring other middleware components 47 | app.use(session({ resave: true, 48 | saveUninitialized: true, 49 | secret: config.sessionSecret})); 50 | app.use(cookieParser('Test')); 51 | app.use(bodyParser.json()); 52 | app.use(bodyParser.urlencoded({ extended: true })); 53 | 54 | app.get('/', function (req, res) { 55 | //Store targetId and RESTURI in a session 56 | (typeof(req.query.proxyRestUri) == 'undefined' || req.query.proxyRestUri === null) ? 57 | req.session.RESTURI = config.RESTURI : 58 | req.session.RESTURI = req.query.proxyRestUri; 59 | 60 | (typeof(req.query.proxyRestUri) == 'undefined' || req.query.proxyRestUri === null) ? 61 | req.session.targetId = config.REDIRECT : 62 | req.session.targetId = req.query.targetId; 63 | 64 | 65 | console.log("Root request, received:", req.query); 66 | console.log("Session targetId: ",req.session.targetId); 67 | console.log("Session RESTURI: ",req.session.RESTURI); 68 | 69 | res.sendfile('SelectUser.htm'); 70 | }); 71 | 72 | app.get('/logout', function (req, res) { 73 | var selectedUser = req.query.selectedUser; 74 | var userDirectory = req.query.userDirectory; 75 | console.log("Logout user: "+selectedUser+" directory: "+userDirectory); 76 | 77 | logout(req,res,selectedUser,userDirectory); 78 | req.session.destroy(); 79 | }); 80 | 81 | app.get('/login', function (req, res) { 82 | var selectedUser = req.query.selectedUser; 83 | var userDirectory = req.query.userDirectory; 84 | 85 | console.log("Login user: ",selectedUser," Directory: ",userDirectory); 86 | 87 | requestticket(req, res, selectedUser, userDirectory, req.session.RESTURI, req.session.targetId); 88 | req.session.destroy(); 89 | }); 90 | 91 | app.get("/resource/font", function (req, res) { 92 | res.sendfile('qlikview-sans.svg'); 93 | }); 94 | 95 | app.get("/resource/icon", function (req, res) { 96 | res.sendfile("users.png"); 97 | }); 98 | 99 | app.get("/resource/qv", function (req, res) { 100 | res.sendfile("QlikLogo-RGB.png"); 101 | }); 102 | 103 | app.get("/resource/background", function (req, res) { 104 | res.sendfile("ConnectingCircles-01.png"); 105 | }); 106 | 107 | 108 | function logout(req, res, selectedUser, userDirectory) { 109 | 110 | //Configure parameters for the logout request 111 | var options = { 112 | host: url.parse(req.session.RESTURI).hostname, 113 | port: url.parse(req.session.RESTURI).port, 114 | path: url.parse(req.session.RESTURI).path+'/user/'+userDirectory.toString()+'/' + selectedUser.toString() + '?xrfkey=aaaaaaaaaaaaaaaa', 115 | method: 'DELETE', 116 | headers: { 'X-qlik-xrfkey': 'aaaaaaaaaaaaaaaa', 'Content-Type': 'application/json' }, 117 | pfx: fs.readFileSync('client.pfx'), 118 | passphrase: config.certificateConfig.passphrase, 119 | rejectUnauthorized: false, 120 | agent: false 121 | }; 122 | 123 | console.log("Path:", options.path.toString()); 124 | //Send request to get logged out 125 | var ticketreq = https.request(options, function (ticketres) { 126 | console.log("statusCode: ", ticketres.statusCode); 127 | //console.log("headers: ", ticketres.headers); 128 | 129 | ticketres.on('data', function (d) { 130 | console.log(selectedUser, " is logged out"); 131 | console.log("DELETE Response:", d.toString()); 132 | 133 | redirectURI = '/'; 134 | 135 | console.log("Logout redirect:", redirectURI); 136 | res.redirect(redirectURI); 137 | }); 138 | }); 139 | 140 | //Send request to logout 141 | ticketreq.end(); 142 | 143 | ticketreq.on('error', function (e) { 144 | console.error('Error' + e); 145 | }); 146 | }; 147 | 148 | 149 | function requestticket(req, res, selecteduser, userdirectory, RESTURI, targetId) { 150 | //Configure parameters for the ticket request 151 | var options = { 152 | host: url.parse(RESTURI).hostname, 153 | port: url.parse(RESTURI).port, 154 | path: url.parse(RESTURI).path + '/ticket?xrfkey=aaaaaaaaaaaaaaaa', 155 | method: 'POST', 156 | headers: { 'X-qlik-xrfkey': 'aaaaaaaaaaaaaaaa', 'Content-Type': 'application/json' }, 157 | pfx: fs.readFileSync('client.pfx'), 158 | passphrase: config.certificateConfig.passphrase, 159 | rejectUnauthorized: false, 160 | agent: false 161 | }; 162 | 163 | console.log("Path:", options.path.toString()); 164 | //Send ticket request 165 | var ticketreq = https.request(options, function (ticketres) { 166 | console.log("statusCode: ", ticketres.statusCode); 167 | //console.log("headers: ", ticketres.headers); 168 | 169 | ticketres.on('data', function (d) { 170 | //Parse ticket response 171 | console.log(selecteduser, " is logged in"); 172 | console.log("POST Response:", d.toString()); 173 | 174 | var ticket = JSON.parse(d.toString()); 175 | 176 | //Add the QlikTicket to the redirect URL regardless whether the existing REDIRECT has existing params. 177 | 178 | console.log("REDIRECT: ",config.REDIRECT); 179 | console.log("targetId: ",targetId); 180 | var myRedirect = url.parse(config.REDIRECT); 181 | 182 | var myQueryString = querystring.parse(myRedirect.query); 183 | myQueryString['QlikTicket'] = ticket.Ticket; 184 | 185 | redirectURI = '/?selecteduser='+ selecteduser; 186 | 187 | //This replaces the existing REDIRECT querystring with the one with the QlikTicket. 188 | if (typeof(myRedirect.query) == 'undefined' || myRedirect.query === null) { 189 | redirectURI += '&QlikRedirect='+ querystring.escape(myRedirect.href + '?' + querystring.stringify(myQueryString)); 190 | } else { 191 | redirectURI += '&QlikRedirect='+ querystring.escape(myRedirect.href.replace(myRedirect.query,querystring.stringify(myQueryString))); 192 | } 193 | 194 | console.log("Login redirect:", redirectURI); 195 | res.redirect(redirectURI); 196 | }); 197 | }); 198 | 199 | //Send JSON request for ticket 200 | var jsonrequest = JSON.stringify({ 'UserDirectory': userdirectory.toString() , 'UserId': selecteduser.toString(), 'Attributes': [] }); 201 | 202 | ticketreq.write(jsonrequest); 203 | ticketreq.end(); 204 | 205 | ticketreq.on('error', function (e) { 206 | console.error('Error' + e); 207 | }); 208 | }; 209 | 210 | //Server options to run an HTTPS server 211 | var httpsoptions = { 212 | pfx: fs.readFileSync('server.pfx'), 213 | passphrase: config.certificateConfig.passphrase 214 | }; 215 | 216 | //Start listener 217 | var server = https.createServer(httpsoptions, app); 218 | server.listen(app.get('port'), function() 219 | { 220 | console.log('Express server listening on port ' + app.get('port')); 221 | }); 222 | -------------------------------------------------------------------------------- /ConnectingCircles-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flautrup/AccessControlTestModule/b7c774b63e3ee43ebbbff383d4f0d7c4608f196d/ConnectingCircles-01.png -------------------------------------------------------------------------------- /QlikLogo-RGB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flautrup/AccessControlTestModule/b7c774b63e3ee43ebbbff383d4f0d7c4608f196d/QlikLogo-RGB.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #AccessControlTestModule 2 | 3 | ##Description: 4 | This is a authentication module to be used for testing access control of the QV product. This should never be used in production scenarios as it is lacking security. 5 | 6 | ##Installation: 7 | * Install nodejs found at http://nodejs.org/ 8 | * Download the AccessControlTestModule.zip 9 | * Unzip AccessControlTestModule 10 | * From the command prompt go to the directory where you unzipped AccessControTestModule 11 | * Enter npm install in the command prompt to install the dependencies for the module. 12 | 13 | ##Setup 14 | * Go to the Qlik Management Console (QMC) and export certificates for the host that AccessControlTestModule is running on with a password (check secrets key checkbox) of your choosing. 15 | * Copy client.pfx and server.pfx certificates from C:\ProgramData\Qlik\Sense\Repository\Exported Certificates\[host] to the directory where you unzipped AccessControlTestModule. Note, if you are not using the Qlik Self Signed Certificate, then export your server.pfx from your OS's Certificate Manager program. 16 | * Edit config.js with the password, config.port, config.RESTURI, and config.REDIRECT of your choice 17 | * From the command prompt go to the directory where you unzipped AccessControTestModule and enter "node AccessControlTestModule.js" 18 | * In QMC, add a virtual proxy to the proxy with prefix "custom", Authentication module redirect URI "https://[server]:[port], (default port is 8185) Session cookie header name to "X-Qlik-Session-custom" and press OK and then Save. 19 | * In QMC, add a User or Login security access rule for your userDirectory. The default userDirectory='QVNCYCLES' 20 | 21 | ##Modify Users: 22 | To add or change users edit the SelectUser.htm file. 23 | 24 | To add a new user add this section in the table, userid is the "value" attribute: 25 | ``` 26 | 27 | ``` 28 | 29 | To change the userDirectory name, change the "name" attribute of the following line: 30 | ``` 31 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 |
71 |
Select a user from the list above.
72 | 73 | 159 | 160 | 161 | 162 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | var config = {} 2 | 3 | //Certificate password used when exporting the certificate. 4 | config.certificateConfig = { 5 | passphrase: 'enterYourCertificatePasswordHere' 6 | }; 7 | 8 | config.sessionSecret='uwotm8'; 9 | 10 | //Port you want Node.js to listen to for authentication requests. 11 | //Changing this requires a change in the Virtual Proxy redirect URI as well. 12 | config.port='8185'; 13 | 14 | //Example RESTURI for API Endpoint. Do not forget to include virtual proxy in path. 15 | //default: config.RESTURI='https://servername.com:4243/qps/custom'; 16 | config.RESTURI='https://enterYourServernameHere.com:4243/qps/custom'; 17 | 18 | //REDIRECT for embedding. Use any of the following paths. Do not forget to include virtual proxy in path. 19 | // 20 | // HUB Example (default): 21 | // config.REDIRECT='https://servername.com/custom/hub'; 22 | // 23 | // SHEET Example: (use Dev-Hub Single Configurator to get your Sheet URL) 24 | // config.REDIRECT='https://servername.com/custom/single?appid=cd29ef8d-7c02-48d3-8d90-b5a40395c316&sheet=LSgtJH&opt=currsel&select=clearall'; 25 | config.REDIRECT='https://enterYourServernameHere.com/custom/hub'; 26 | 27 | module.exports = config; 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "AccessControlTestModule", 3 | "description": "Qlik Sense Ticket API Authentication module to test access control in Qlik Sense", 4 | "version": "0.0.2", 5 | "private": true, 6 | "dependencies": { 7 | "body-parser": "^1.12.4", 8 | "cookie-parser": "^1.3.5", 9 | "express": "^4.12.4", 10 | "express-session": "^1.11.2" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /qlikview-sans.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | -------------------------------------------------------------------------------- /users.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flautrup/AccessControlTestModule/b7c774b63e3ee43ebbbff383d4f0d7c4608f196d/users.png --------------------------------------------------------------------------------