Merge pull request #609 from yuya-oc/permission-request-handler
Dialog to confirm requested permissions
This commit is contained in:
@@ -25,7 +25,9 @@ const MainPage = createReactClass({
|
|||||||
useSpellChecker: PropTypes.bool.isRequired,
|
useSpellChecker: PropTypes.bool.isRequired,
|
||||||
onSelectSpellCheckerLocale: PropTypes.func.isRequired,
|
onSelectSpellCheckerLocale: PropTypes.func.isRequired,
|
||||||
deeplinkingUrl: PropTypes.string,
|
deeplinkingUrl: PropTypes.string,
|
||||||
showAddServerButton: PropTypes.bool.isRequired
|
showAddServerButton: PropTypes.bool.isRequired,
|
||||||
|
requestingPermission: TabBar.propTypes.requestingPermission,
|
||||||
|
onClickPermissionDialog: PropTypes.func
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState() {
|
getInitialState() {
|
||||||
@@ -260,6 +262,8 @@ const MainPage = createReactClass({
|
|||||||
onSelect={this.handleSelect}
|
onSelect={this.handleSelect}
|
||||||
onAddServer={this.addServer}
|
onAddServer={this.addServer}
|
||||||
showAddServerButton={this.props.showAddServerButton}
|
showAddServerButton={this.props.showAddServerButton}
|
||||||
|
requestingPermission={this.props.requestingPermission}
|
||||||
|
onClickPermissionDialog={this.props.onClickPermissionDialog}
|
||||||
/>
|
/>
|
||||||
</Row>
|
</Row>
|
||||||
);
|
);
|
||||||
|
89
src/browser/components/PermissionRequestDialog.jsx
Normal file
89
src/browser/components/PermissionRequestDialog.jsx
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
const React = require('react');
|
||||||
|
const PropTypes = require('prop-types');
|
||||||
|
const {Button, Glyphicon, Popover} = require('react-bootstrap');
|
||||||
|
|
||||||
|
const PERMISSIONS = {
|
||||||
|
media: {
|
||||||
|
description: 'Use your camera and microphone',
|
||||||
|
glyph: 'facetime-video'
|
||||||
|
},
|
||||||
|
geolocation: {
|
||||||
|
description: 'Know your location',
|
||||||
|
glyph: 'map-marker'
|
||||||
|
},
|
||||||
|
notifications: {
|
||||||
|
description: 'Show notifications',
|
||||||
|
glyph: 'bell'
|
||||||
|
},
|
||||||
|
midiSysex: {
|
||||||
|
description: 'Use your MIDI devices',
|
||||||
|
glyph: 'music'
|
||||||
|
},
|
||||||
|
pointerLock: {
|
||||||
|
description: 'Lock your mouse cursor',
|
||||||
|
glyph: 'hand-up'
|
||||||
|
},
|
||||||
|
fullscreen: {
|
||||||
|
description: 'Enter full screen',
|
||||||
|
glyph: 'resize-full'
|
||||||
|
},
|
||||||
|
openExternal: {
|
||||||
|
description: 'Open external',
|
||||||
|
glyph: 'new-window'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function glyph(permission) {
|
||||||
|
const data = PERMISSIONS[permission];
|
||||||
|
if (data) {
|
||||||
|
return data.glyph;
|
||||||
|
}
|
||||||
|
return 'alert';
|
||||||
|
}
|
||||||
|
|
||||||
|
function description(permission) {
|
||||||
|
const data = PERMISSIONS[permission];
|
||||||
|
if (data) {
|
||||||
|
return data.description;
|
||||||
|
}
|
||||||
|
return `Be granted "${permission}" permission`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function PermissionRequestDialog(props) {
|
||||||
|
const {origin, permission, onClickAllow, onClickBlock, onClickClose, ...reft} = props;
|
||||||
|
return (
|
||||||
|
<Popover
|
||||||
|
className='PermissionRequestDialog'
|
||||||
|
{...reft}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className='PermissionRequestDialog-content'
|
||||||
|
>
|
||||||
|
<p>{`${origin} wants to:`}</p>
|
||||||
|
<p className='PermissionRequestDialog-content-description'>
|
||||||
|
<Glyphicon glyph={glyph(permission)}/>
|
||||||
|
{description(permission)}
|
||||||
|
</p>
|
||||||
|
<p className='PermissionRequestDialog-content-buttons'>
|
||||||
|
<Button onClick={onClickAllow}>{'Allow'}</Button>
|
||||||
|
<Button onClick={onClickBlock}>{'Block'}</Button>
|
||||||
|
</p>
|
||||||
|
<Button
|
||||||
|
className='PermissionRequestDialog-content-close'
|
||||||
|
bsStyle='link'
|
||||||
|
onClick={onClickClose}
|
||||||
|
>{'×'}</Button>
|
||||||
|
</div>
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
PermissionRequestDialog.propTypes = {
|
||||||
|
origin: PropTypes.string.isRequired,
|
||||||
|
permission: PropTypes.oneOf(['media', 'geolocation', 'notifications', 'midiSysex', 'pointerLock', 'fullscreen', 'openExternal']).isRequired,
|
||||||
|
onClickAllow: PropTypes.func,
|
||||||
|
onClickBlock: PropTypes.func,
|
||||||
|
onClickClose: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = PermissionRequestDialog;
|
@@ -1,34 +1,68 @@
|
|||||||
const React = require('react');
|
const React = require('react');
|
||||||
|
const {findDOMNode} = require('react-dom');
|
||||||
const PropTypes = require('prop-types');
|
const PropTypes = require('prop-types');
|
||||||
const {Glyphicon, Nav, NavItem} = require('react-bootstrap');
|
const {Glyphicon, Nav, NavItem, Overlay} = require('react-bootstrap');
|
||||||
|
const PermissionRequestDialog = require('./PermissionRequestDialog.jsx');
|
||||||
|
|
||||||
function TabBar(props) {
|
class TabBar extends React.Component { // need "this"
|
||||||
const tabs = props.teams.map((team, index) => {
|
render() {
|
||||||
let unreadCount = 0;
|
const tabs = this.props.teams.map((team, index) => {
|
||||||
if (props.unreadCounts[index] > 0) {
|
let unreadCount = 0;
|
||||||
unreadCount = props.unreadCounts[index];
|
if (this.props.unreadCounts[index] > 0) {
|
||||||
}
|
unreadCount = this.props.unreadCounts[index];
|
||||||
if (props.unreadAtActive[index]) {
|
}
|
||||||
unreadCount += 1;
|
if (this.props.unreadAtActive[index]) {
|
||||||
}
|
unreadCount += 1;
|
||||||
|
}
|
||||||
|
|
||||||
let mentionCount = 0;
|
let mentionCount = 0;
|
||||||
if (props.mentionCounts[index] > 0) {
|
if (this.props.mentionCounts[index] > 0) {
|
||||||
mentionCount = props.mentionCounts[index];
|
mentionCount = this.props.mentionCounts[index];
|
||||||
}
|
}
|
||||||
if (props.mentionAtActiveCounts[index] > 0) {
|
if (this.props.mentionAtActiveCounts[index] > 0) {
|
||||||
mentionCount += props.mentionAtActiveCounts[index];
|
mentionCount += this.props.mentionAtActiveCounts[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
let badgeDiv;
|
let badgeDiv;
|
||||||
if (mentionCount !== 0) {
|
if (mentionCount !== 0) {
|
||||||
badgeDiv = (
|
badgeDiv = (
|
||||||
<div className='TabBar-badge'>
|
<div className='TabBar-badge'>
|
||||||
{mentionCount}
|
{mentionCount}
|
||||||
</div>);
|
</div>);
|
||||||
}
|
}
|
||||||
const id = 'teamTabItem' + index;
|
const id = 'teamTabItem' + index;
|
||||||
if (unreadCount === 0) {
|
const permissionOverlay = this.props.requestingPermission[index] ? (
|
||||||
|
<Overlay
|
||||||
|
className='TabBar-permissionOverlay'
|
||||||
|
placement='bottom'
|
||||||
|
show={this.props.activeKey === index}
|
||||||
|
target={() => findDOMNode(this.refs[id])}
|
||||||
|
>
|
||||||
|
<PermissionRequestDialog
|
||||||
|
id={`${id}-permissionDialog`}
|
||||||
|
origin={this.props.requestingPermission[index].origin}
|
||||||
|
permission={this.props.requestingPermission[index].permission}
|
||||||
|
onClickAllow={this.props.onClickPermissionDialog.bind(null, index, 'allow')}
|
||||||
|
onClickBlock={this.props.onClickPermissionDialog.bind(null, index, 'block')}
|
||||||
|
onClickClose={this.props.onClickPermissionDialog.bind(null, index, 'close')}
|
||||||
|
/>
|
||||||
|
</Overlay>
|
||||||
|
) : null;
|
||||||
|
if (unreadCount === 0) {
|
||||||
|
return (
|
||||||
|
<NavItem
|
||||||
|
className='teamTabItem'
|
||||||
|
key={id}
|
||||||
|
id={id}
|
||||||
|
eventKey={index}
|
||||||
|
ref={id}
|
||||||
|
>
|
||||||
|
{ team.name }
|
||||||
|
{ ' ' }
|
||||||
|
{ badgeDiv }
|
||||||
|
{permissionOverlay}
|
||||||
|
</NavItem>);
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<NavItem
|
<NavItem
|
||||||
className='teamTabItem'
|
className='teamTabItem'
|
||||||
@@ -36,53 +70,42 @@ function TabBar(props) {
|
|||||||
id={id}
|
id={id}
|
||||||
eventKey={index}
|
eventKey={index}
|
||||||
>
|
>
|
||||||
{ team.name }
|
<b>{ team.name }</b>
|
||||||
{ ' ' }
|
{ ' ' }
|
||||||
{ badgeDiv }
|
{ badgeDiv }
|
||||||
</NavItem>);
|
</NavItem>);
|
||||||
|
});
|
||||||
|
if (this.props.showAddServerButton === true) {
|
||||||
|
tabs.push(
|
||||||
|
<NavItem
|
||||||
|
className='TabBar-addServerButton'
|
||||||
|
key='addServerButton'
|
||||||
|
id='addServerButton'
|
||||||
|
eventKey='addServerButton'
|
||||||
|
title='Add new server'
|
||||||
|
>
|
||||||
|
<Glyphicon glyph='plus'/>
|
||||||
|
</NavItem>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<NavItem
|
<Nav
|
||||||
className='teamTabItem'
|
className='TabBar'
|
||||||
key={id}
|
id={this.props.id}
|
||||||
id={id}
|
bsStyle='tabs'
|
||||||
eventKey={index}
|
activeKey={this.props.activeKey}
|
||||||
|
onSelect={(eventKey) => {
|
||||||
|
if (eventKey === 'addServerButton') {
|
||||||
|
this.props.onAddServer();
|
||||||
|
} else {
|
||||||
|
this.props.onSelect(eventKey);
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<b>{ team.name }</b>
|
{ tabs }
|
||||||
{ ' ' }
|
</Nav>
|
||||||
{ badgeDiv }
|
|
||||||
</NavItem>);
|
|
||||||
});
|
|
||||||
if (props.showAddServerButton === true) {
|
|
||||||
tabs.push(
|
|
||||||
<NavItem
|
|
||||||
className='TabBar-addServerButton'
|
|
||||||
key='addServerButton'
|
|
||||||
id='addServerButton'
|
|
||||||
eventKey='addServerButton'
|
|
||||||
title='Add new server'
|
|
||||||
>
|
|
||||||
<Glyphicon glyph='plus'/>
|
|
||||||
</NavItem>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
|
||||||
<Nav
|
|
||||||
className='TabBar'
|
|
||||||
id={props.id}
|
|
||||||
bsStyle='tabs'
|
|
||||||
activeKey={props.activeKey}
|
|
||||||
onSelect={(eventKey) => {
|
|
||||||
if (eventKey === 'addServerButton') {
|
|
||||||
props.onAddServer();
|
|
||||||
} else {
|
|
||||||
props.onSelect(eventKey);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{ tabs }
|
|
||||||
</Nav>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TabBar.propTypes = {
|
TabBar.propTypes = {
|
||||||
@@ -94,8 +117,13 @@ TabBar.propTypes = {
|
|||||||
unreadAtActive: PropTypes.array,
|
unreadAtActive: PropTypes.array,
|
||||||
mentionCounts: PropTypes.array,
|
mentionCounts: PropTypes.array,
|
||||||
mentionAtActiveCounts: PropTypes.array,
|
mentionAtActiveCounts: PropTypes.array,
|
||||||
|
showAddServerButton: PropTypes.bool,
|
||||||
|
requestingPermission: PropTypes.arrayOf(PropTypes.shape({
|
||||||
|
origin: PropTypes.string,
|
||||||
|
permission: PropTypes.string
|
||||||
|
})),
|
||||||
onAddServer: PropTypes.func,
|
onAddServer: PropTypes.func,
|
||||||
showAddServerButton: PropTypes.bool
|
onClickPermissionDialog: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = TabBar;
|
module.exports = TabBar;
|
||||||
|
44
src/browser/css/components/PermissionRequestDialog.css
Normal file
44
src/browser/css/components/PermissionRequestDialog.css
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
.PermissionRequestDialog-content{
|
||||||
|
min-width: 250px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.PermissionRequestDialog-content .popover-content {
|
||||||
|
padding: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.PermissionRequestDialog-content > *:nth-child(1) {
|
||||||
|
margin-right: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.PermissionRequestDialog-content .PermissionRequestDialog-content-description {
|
||||||
|
text-indent: 0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.PermissionRequestDialog-content .PermissionRequestDialog-content-description .glyphicon {
|
||||||
|
margin-right: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.PermissionRequestDialog-content .PermissionRequestDialog-content-buttons {
|
||||||
|
margin-bottom: 0;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.PermissionRequestDialog-content .PermissionRequestDialog-content-buttons > * {
|
||||||
|
margin-right: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.PermissionRequestDialog-content .PermissionRequestDialog-content-buttons > *:last-child {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.PermissionRequestDialog-content .PermissionRequestDialog-content-close {
|
||||||
|
position:absolute;
|
||||||
|
top: 0px;
|
||||||
|
right:0px;
|
||||||
|
color: gray;
|
||||||
|
text-decoration-line: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.PermissionRequestDialog-content .PermissionRequestDialog-content-close:hover {
|
||||||
|
color: black;
|
||||||
|
}
|
@@ -36,4 +36,8 @@
|
|||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
};
|
}
|
||||||
|
|
||||||
|
div[id*="-permissionDialog"] {
|
||||||
|
max-width: 350px;
|
||||||
|
}
|
||||||
|
@@ -3,5 +3,6 @@
|
|||||||
@import url("MainPage.css");
|
@import url("MainPage.css");
|
||||||
@import url("MattermostView.css");
|
@import url("MattermostView.css");
|
||||||
@import url("NewTeamModal.css");
|
@import url("NewTeamModal.css");
|
||||||
|
@import url("PermissionRequestDialog.css");
|
||||||
@import url("TabBar.css");
|
@import url("TabBar.css");
|
||||||
@import url("TeamListItem.css");
|
@import url("TeamListItem.css");
|
||||||
|
@@ -15,6 +15,7 @@ const AppConfig = require('./config/AppConfig.js');
|
|||||||
const url = require('url');
|
const url = require('url');
|
||||||
|
|
||||||
const badge = require('./js/badge');
|
const badge = require('./js/badge');
|
||||||
|
const utils = require('../utils/util');
|
||||||
|
|
||||||
remote.getCurrentWindow().removeAllListeners('focus');
|
remote.getCurrentWindow().removeAllListeners('focus');
|
||||||
|
|
||||||
@@ -86,12 +87,59 @@ function showUnreadBadge(unreadCount, mentionCount) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const permissionRequestQueue = [];
|
||||||
|
const requestingPermission = new Array(AppConfig.data.teams.length);
|
||||||
|
|
||||||
function teamConfigChange(teams) {
|
function teamConfigChange(teams) {
|
||||||
AppConfig.set('teams', teams);
|
AppConfig.set('teams', teams);
|
||||||
|
requestingPermission.length = teams.length;
|
||||||
ipcRenderer.send('update-menu', AppConfig.data);
|
ipcRenderer.send('update-menu', AppConfig.data);
|
||||||
ipcRenderer.send('update-config');
|
ipcRenderer.send('update-config');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function feedPermissionRequest() {
|
||||||
|
const webviews = document.getElementsByTagName('webview');
|
||||||
|
const webviewOrigins = Array.from(webviews).map((w) => utils.getDomain(w.getAttribute('src')));
|
||||||
|
for (let index = 0; index < requestingPermission.length; index++) {
|
||||||
|
if (requestingPermission[index]) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
for (const request of permissionRequestQueue) {
|
||||||
|
if (request.origin === webviewOrigins[index]) {
|
||||||
|
requestingPermission[index] = request;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleClickPermissionDialog(index, status) {
|
||||||
|
const requesting = requestingPermission[index];
|
||||||
|
ipcRenderer.send('update-permission', requesting.origin, requesting.permission, status);
|
||||||
|
if (status === 'allow' || status === 'block') {
|
||||||
|
const newRequests = permissionRequestQueue.filter((request) => {
|
||||||
|
if (request.permission === requesting.permission && request.origin === requesting.origin) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
permissionRequestQueue.splice(0, permissionRequestQueue.length, ...newRequests);
|
||||||
|
} else if (status === 'close') {
|
||||||
|
const i = permissionRequestQueue.findIndex((e) => e.permission === requesting.permission && e.origin === requesting.origin);
|
||||||
|
permissionRequestQueue.splice(i, 1);
|
||||||
|
}
|
||||||
|
requestingPermission[index] = null;
|
||||||
|
feedPermissionRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
ipcRenderer.on('request-permission', (event, origin, permission) => {
|
||||||
|
if (permissionRequestQueue.length >= 100) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
permissionRequestQueue.push({origin, permission});
|
||||||
|
feedPermissionRequest();
|
||||||
|
});
|
||||||
|
|
||||||
function handleSelectSpellCheckerLocale(locale) {
|
function handleSelectSpellCheckerLocale(locale) {
|
||||||
console.log(locale);
|
console.log(locale);
|
||||||
AppConfig.set('spellCheckerLocale', locale);
|
AppConfig.set('spellCheckerLocale', locale);
|
||||||
@@ -117,6 +165,8 @@ ReactDOM.render(
|
|||||||
onSelectSpellCheckerLocale={handleSelectSpellCheckerLocale}
|
onSelectSpellCheckerLocale={handleSelectSpellCheckerLocale}
|
||||||
deeplinkingUrl={deeplinkingUrl}
|
deeplinkingUrl={deeplinkingUrl}
|
||||||
showAddServerButton={AppConfig.data.enableServerManagement}
|
showAddServerButton={AppConfig.data.enableServerManagement}
|
||||||
|
requestingPermission={requestingPermission}
|
||||||
|
onClickPermissionDialog={handleClickPermissionDialog}
|
||||||
/>,
|
/>,
|
||||||
document.getElementById('content')
|
document.getElementById('content')
|
||||||
);
|
);
|
||||||
|
@@ -37,6 +37,7 @@ const appMenu = require('./main/menus/app');
|
|||||||
const trayMenu = require('./main/menus/tray');
|
const trayMenu = require('./main/menus/tray');
|
||||||
const downloadURL = require('./main/downloadURL');
|
const downloadURL = require('./main/downloadURL');
|
||||||
const allowProtocolDialog = require('./main/allowProtocolDialog');
|
const allowProtocolDialog = require('./main/allowProtocolDialog');
|
||||||
|
const permissionRequestHandler = require('./main/permissionRequestHandler');
|
||||||
|
|
||||||
const SpellChecker = require('./main/SpellChecker');
|
const SpellChecker = require('./main/SpellChecker');
|
||||||
|
|
||||||
@@ -577,6 +578,9 @@ app.on('ready', () => {
|
|||||||
});
|
});
|
||||||
ipcMain.emit('update-dict');
|
ipcMain.emit('update-dict');
|
||||||
|
|
||||||
|
const permissionFile = path.join(app.getPath('userData'), 'permission.json');
|
||||||
|
session.defaultSession.setPermissionRequestHandler(permissionRequestHandler(mainWindow, permissionFile));
|
||||||
|
|
||||||
// Open the DevTools.
|
// Open the DevTools.
|
||||||
// mainWindow.openDevTools();
|
// mainWindow.openDevTools();
|
||||||
});
|
});
|
||||||
|
114
src/main/permissionRequestHandler.js
Normal file
114
src/main/permissionRequestHandler.js
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
const {ipcMain} = require('electron');
|
||||||
|
const {URL} = require('url');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
const PERMISSION_GRANTED = 'granted';
|
||||||
|
const PERMISSION_DENIED = 'denied';
|
||||||
|
|
||||||
|
class PermissionManager {
|
||||||
|
constructor(file) {
|
||||||
|
this.file = file;
|
||||||
|
if (fs.existsSync(file)) {
|
||||||
|
this.permissions = JSON.parse(fs.readFileSync(this.file, 'utf-8'));
|
||||||
|
} else {
|
||||||
|
this.permissions = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writeFileSync() {
|
||||||
|
fs.writeFileSync(this.file, JSON.stringify(this.permissions, null, ' '));
|
||||||
|
}
|
||||||
|
|
||||||
|
grant(origin, permission) {
|
||||||
|
if (!this.permissions[origin]) {
|
||||||
|
this.permissions[origin] = {};
|
||||||
|
}
|
||||||
|
this.permissions[origin][permission] = PERMISSION_GRANTED;
|
||||||
|
this.writeFileSync();
|
||||||
|
}
|
||||||
|
|
||||||
|
deny(origin, permission) {
|
||||||
|
if (!this.permissions[origin]) {
|
||||||
|
this.permissions[origin] = {};
|
||||||
|
}
|
||||||
|
this.permissions[origin][permission] = PERMISSION_DENIED;
|
||||||
|
this.writeFileSync();
|
||||||
|
}
|
||||||
|
|
||||||
|
clear(origin, permission) {
|
||||||
|
delete this.permissions[origin][permission];
|
||||||
|
}
|
||||||
|
|
||||||
|
isGranted(origin, permission) {
|
||||||
|
if (this.permissions[origin]) {
|
||||||
|
return this.permissions[origin][permission] === PERMISSION_GRANTED;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
isDenied(origin, permission) {
|
||||||
|
if (this.permissions[origin]) {
|
||||||
|
return this.permissions[origin][permission] === PERMISSION_DENIED;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function dequeueRequests(requestQueue, permissionManager, origin, permission, status) {
|
||||||
|
switch (status) {
|
||||||
|
case 'allow':
|
||||||
|
permissionManager.grant(origin, permission);
|
||||||
|
break;
|
||||||
|
case 'block':
|
||||||
|
permissionManager.deny(origin, permission);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (status === 'allow' || status === 'block') {
|
||||||
|
const newQueue = requestQueue.filter((request) => {
|
||||||
|
if (request.origin === origin && request.permission === permission) {
|
||||||
|
request.callback(status === 'allow');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
requestQueue.splice(0, requestQueue.length, ...newQueue);
|
||||||
|
} else {
|
||||||
|
const index = requestQueue.findIndex((request) => {
|
||||||
|
return request.origin === origin && request.permission === permission;
|
||||||
|
});
|
||||||
|
requestQueue[index].callback(false);
|
||||||
|
requestQueue.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function permissionRequestHandler(mainWindow, permissionFile) {
|
||||||
|
const permissionManager = new PermissionManager(permissionFile);
|
||||||
|
const requestQueue = [];
|
||||||
|
ipcMain.on('update-permission', (event, origin, permission, status) => {
|
||||||
|
dequeueRequests(requestQueue, permissionManager, origin, permission, status);
|
||||||
|
});
|
||||||
|
return (webContents, permission, callback) => {
|
||||||
|
const targetURL = new URL(webContents.getURL());
|
||||||
|
if (permissionManager.isDenied(targetURL.origin, permission)) {
|
||||||
|
callback(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (permissionManager.isGranted(targetURL.origin, permission)) {
|
||||||
|
callback(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
requestQueue.push({
|
||||||
|
origin: targetURL.origin,
|
||||||
|
permission,
|
||||||
|
callback
|
||||||
|
});
|
||||||
|
mainWindow.webContents.send('request-permission', targetURL.origin, permission);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
permissionRequestHandler.PermissionManager = PermissionManager;
|
||||||
|
|
||||||
|
module.exports = permissionRequestHandler;
|
@@ -23,6 +23,7 @@ const mattermostURL = 'http://example.com/team';
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
sourceRootDir,
|
sourceRootDir,
|
||||||
configFilePath,
|
configFilePath,
|
||||||
|
userDataDir,
|
||||||
boundsInfoPath,
|
boundsInfoPath,
|
||||||
mattermostURL,
|
mattermostURL,
|
||||||
|
|
||||||
|
80
test/specs/permisson_test.js
Normal file
80
test/specs/permisson_test.js
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
/* eslint-disable no-unused-expressions */
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const env = require('../modules/environment');
|
||||||
|
const {PermissionManager} = require('../../src/main/permissionRequestHandler');
|
||||||
|
|
||||||
|
const permissionFile = path.join(env.userDataDir, 'permission.json');
|
||||||
|
|
||||||
|
describe('PermissionManager', function() {
|
||||||
|
beforeEach(function(done) {
|
||||||
|
fs.unlink(permissionFile, () => {
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should grant a permisson for an origin', function() {
|
||||||
|
const ORIGIN = 'origin';
|
||||||
|
const PERMISSION = 'permission';
|
||||||
|
const manager = new PermissionManager(permissionFile);
|
||||||
|
|
||||||
|
manager.isGranted(ORIGIN, PERMISSION).should.be.false;
|
||||||
|
manager.isDenied(ORIGIN, PERMISSION).should.be.false;
|
||||||
|
|
||||||
|
manager.grant(ORIGIN, PERMISSION);
|
||||||
|
|
||||||
|
manager.isGranted(ORIGIN, PERMISSION).should.be.true;
|
||||||
|
manager.isDenied(ORIGIN, PERMISSION).should.be.false;
|
||||||
|
|
||||||
|
manager.isGranted(ORIGIN + '_another', PERMISSION).should.be.false;
|
||||||
|
manager.isGranted(ORIGIN, PERMISSION + '_another').should.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should deny a permisson for an origin', function() {
|
||||||
|
const ORIGIN = 'origin';
|
||||||
|
const PERMISSION = 'permission';
|
||||||
|
const manager = new PermissionManager(permissionFile);
|
||||||
|
|
||||||
|
manager.isGranted(ORIGIN, PERMISSION).should.be.false;
|
||||||
|
manager.isDenied(ORIGIN, PERMISSION).should.be.false;
|
||||||
|
|
||||||
|
manager.deny(ORIGIN, PERMISSION);
|
||||||
|
|
||||||
|
manager.isGranted(ORIGIN, PERMISSION).should.be.false;
|
||||||
|
manager.isDenied(ORIGIN, PERMISSION).should.be.true;
|
||||||
|
|
||||||
|
manager.isDenied(ORIGIN + '_another', PERMISSION).should.be.false;
|
||||||
|
manager.isDenied(ORIGIN, PERMISSION + '_another').should.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should save permissons to the file', function() {
|
||||||
|
const ORIGIN = 'origin';
|
||||||
|
const PERMISSION = 'permission';
|
||||||
|
const manager = new PermissionManager(permissionFile);
|
||||||
|
manager.deny(ORIGIN, PERMISSION);
|
||||||
|
manager.grant(ORIGIN + '_another', PERMISSION + '_another');
|
||||||
|
JSON.parse(fs.readFileSync(permissionFile)).should.deep.equal({
|
||||||
|
origin: {
|
||||||
|
permission: 'denied'
|
||||||
|
},
|
||||||
|
origin_another: {
|
||||||
|
permission_another: 'granted'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should restore permissions from the file', function() {
|
||||||
|
fs.writeFileSync(permissionFile, JSON.stringify({
|
||||||
|
origin: {
|
||||||
|
permission: 'denied'
|
||||||
|
},
|
||||||
|
origin_another: {
|
||||||
|
permission_another: 'granted'
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
const manager = new PermissionManager(permissionFile);
|
||||||
|
manager.isDenied('origin', 'permission').should.be.true;
|
||||||
|
manager.isGranted('origin_another', 'permission_another').should.be.true;
|
||||||
|
});
|
||||||
|
});
|
Reference in New Issue
Block a user