diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index 149d98c6e6621..9624301abec2b 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -378,9 +378,21 @@ void OnClientCertificateSelected( v8::Isolate* isolate, std::shared_ptr delegate, mate::Arguments* args) { + if (args->Length() == 2) { + delegate->ContinueWithCertificate(nullptr); + return; + } + + v8::Local val; + args->GetNext(&val); + if (val->IsNull()) { + delegate->ContinueWithCertificate(nullptr); + return; + } + mate::Dictionary cert_data; - if (!args->GetNext(&cert_data)) { - args->ThrowError(); + if (!mate::ConvertFromV8(isolate, val, &cert_data)) { + args->ThrowError("Must pass valid certificate object."); return; } diff --git a/docs/api/app.md b/docs/api/app.md index de1ae1138f9a1..a43de112f62c3 100644 --- a/docs/api/app.md +++ b/docs/api/app.md @@ -113,7 +113,7 @@ Returns: * `url` String Emitted when the user wants to open a URL with the application. Your application's -`Info.plist` file must define the url scheme within the `CFBundleURLTypes` key, and +`Info.plist` file must define the url scheme within the `CFBundleURLTypes` key, and set `NSPrincipalClass` to `AtomApplication`. You should call `event.preventDefault()` if you want to handle this event. @@ -222,12 +222,12 @@ Returns: * `url` URL * `certificateList` [Certificate[]](structures/certificate.md) * `callback` Function - * `certificate` [Certificate](structures/certificate.md) + * `certificate` [Certificate](structures/certificate.md) (optional) Emitted when a client certificate is requested. The `url` corresponds to the navigation entry requesting the client certificate -and `callback` needs to be called with an entry filtered from the list. Using +and `callback` can be called with an entry filtered from the list. Using `event.preventDefault()` prevents the application from using the first certificate from the store. diff --git a/spec/api-app-spec.js b/spec/api-app-spec.js index d1ceef1434c43..cf25dde6e5574 100644 --- a/spec/api-app-spec.js +++ b/spec/api-app-spec.js @@ -4,7 +4,7 @@ const https = require('https') const net = require('net') const fs = require('fs') const path = require('path') -const {remote} = require('electron') +const {ipcRenderer, remote} = require('electron') const {closeWindow} = require('./window-helpers') const {app, BrowserWindow, ipcMain} = remote @@ -41,6 +41,41 @@ describe('electron module', function () { }) describe('app module', function () { + let server, secureUrl + const certPath = path.join(__dirname, 'fixtures', 'certificates') + + before(function () { + const options = { + key: fs.readFileSync(path.join(certPath, 'server.key')), + cert: fs.readFileSync(path.join(certPath, 'server.pem')), + ca: [ + fs.readFileSync(path.join(certPath, 'rootCA.pem')), + fs.readFileSync(path.join(certPath, 'intermediateCA.pem')) + ], + requestCert: true, + rejectUnauthorized: false + } + + server = https.createServer(options, function (req, res) { + if (req.client.authorized) { + res.writeHead(200) + res.end('authorized') + } else { + res.writeHead(401) + res.end('denied') + } + }) + + server.listen(0, '127.0.0.1', function () { + const port = server.address().port + secureUrl = `https://127.0.0.1:${port}` + }) + }) + + after(function () { + server.close() + }) + describe('app.getVersion()', function () { it('returns the version field of package.json', function () { assert.equal(app.getVersion(), '0.1.0') @@ -165,24 +200,6 @@ describe('app module', function () { if (process.platform !== 'linux') return var w = null - var certPath = path.join(__dirname, 'fixtures', 'certificates') - var options = { - key: fs.readFileSync(path.join(certPath, 'server.key')), - cert: fs.readFileSync(path.join(certPath, 'server.pem')), - ca: [ - fs.readFileSync(path.join(certPath, 'rootCA.pem')), - fs.readFileSync(path.join(certPath, 'intermediateCA.pem')) - ], - requestCert: true, - rejectUnauthorized: false - } - - var server = https.createServer(options, function (req, res) { - if (req.client.authorized) { - res.writeHead(200) - res.end('authorized') - } - }) afterEach(function () { return closeWindow(w).then(function () { w = null }) @@ -199,25 +216,24 @@ describe('app module', function () { }) w.webContents.on('did-finish-load', function () { - server.close() + assert.equal(w.webContents.getTitle(), 'authorized') done() }) - app.on('select-client-certificate', function (event, webContents, url, list, callback) { + ipcRenderer.once('select-client-certificate', function (event, webContentsId, list) { + assert.equal(webContentsId, w.webContents.id) assert.equal(list.length, 1) assert.equal(list[0].issuerName, 'Intermediate CA') assert.equal(list[0].subjectName, 'Client Cert') assert.equal(list[0].issuer.commonName, 'Intermediate CA') assert.equal(list[0].subject.commonName, 'Client Cert') - callback(list[0]) + event.sender.send('client-certificate-response', list[0]) }) app.importCertificate(options, function (result) { assert(!result) - server.listen(0, '127.0.0.1', function () { - var port = server.address().port - w.loadURL(`https://127.0.0.1:${port}`) - }) + ipcRenderer.sendSync('set-client-certificate-option', false) + w.loadURL(secureUrl) }) }) }) @@ -359,4 +375,32 @@ describe('app module', function () { assert.equal(app.getPath('music'), __dirname) }) }) + + describe('select-client-certificate event', function () { + let w = null + + beforeEach(function () { + w = new BrowserWindow({ + show: false, + webPreferences: { + partition: 'empty-certificate' + } + }) + }) + + afterEach(function () { + return closeWindow(w).then(function () { w = null }) + }) + + it('can respond with empty certificate list', function (done) { + w.webContents.on('did-finish-load', function () { + assert.equal(w.webContents.getTitle(), 'denied') + server.close() + done() + }) + + ipcRenderer.sendSync('set-client-certificate-option', true) + w.webContents.loadURL(secureUrl) + }) + }) }) diff --git a/spec/static/main.js b/spec/static/main.js index de599988dfa31..19b07399301e2 100644 --- a/spec/static/main.js +++ b/spec/static/main.js @@ -206,3 +206,18 @@ app.on('ready', function () { } }) }) + +ipcMain.on('set-client-certificate-option', function (event, skip) { + app.once('select-client-certificate', function (event, webContents, url, list, callback) { + event.preventDefault() + if (skip) { + callback() + } else { + ipcMain.on('client-certificate-response', function (event, certificate) { + callback(certificate) + }) + window.webContents.send('select-client-certificate', webContents.id, list) + } + }) + event.returnValue = 'done' +})