implemented set-login for extensions

This commit is contained in:
antelle 2021-04-27 13:58:43 +02:00
parent 946a063993
commit f52a00a156
No known key found for this signature in database
GPG Key ID: 63C9777AAB7C563C
7 changed files with 209 additions and 6 deletions

View File

@ -10,12 +10,16 @@ import { RuntimeInfo } from 'const/runtime-info';
import { KnownAppVersions } from 'const/known-app-versions';
import { ExtensionConnectView } from 'views/extension/extension-connect-view';
import { ExtensionCreateGroupView } from 'views/extension/extension-create-group-view';
import { ExtensionSaveEntryView } from 'views/extension/extension-save-entry-view';
import { RuntimeDataModel } from 'models/runtime-data-model';
import { AppSettingsModel } from 'models/app-settings-model';
import { Timeouts } from 'const/timeouts';
const KeeWebAssociationId = 'KeeWeb';
const KeeWebHash = '398d9c782ec76ae9e9877c2321cbda2b31fc6d18ccf0fed5ca4bd746bab4d64a'; // sha256('KeeWeb')
const ExtensionGroupIconId = 1;
const DefaultExtensionGroupName = 'Browser';
const ExtensionGroupNames = new Set(['KeePassXC-Browser Passwords', DefaultExtensionGroupName]);
const Errors = {
noOpenFiles: {
@ -404,10 +408,123 @@ const ProtocolHandlers = {
},
async 'set-login'(request) {
decryptRequest(request);
const payload = decryptRequest(request);
await checkContentRequestPermissions(request);
throw new Error('Not implemented');
if (payload.uuid || payload.groupUuid) {
throw new Error('Modirying entries is not supported');
}
if (!payload.url) {
throw new Error('Empty url');
}
const url = new URL(payload.url);
const files = getAvailableFiles(request);
const client = getClient(request);
let selectedGroup;
if (client.permissions.askSave === 'auto' && client.permissions.saveTo) {
const file = files.find((f) => f.id === client.permissions.saveTo.fileId);
selectedGroup = file?.getGroup(client.permissions.saveTo.groupId);
}
if (client.permissions.askSave !== 'auto' || !selectedGroup) {
if (!selectedGroup && RuntimeDataModel.extensionSaveConfig) {
const file = files.find(
(f) => f.id === RuntimeDataModel.extensionSaveConfig.fileId
);
selectedGroup = file?.getGroup(RuntimeDataModel.extensionSaveConfig.groupId);
}
const allGroups = [];
for (const file of files) {
file.forEachGroup((group) => {
const spaces = [];
for (let parent = group; parent.parentGroup; parent = parent.parentGroup) {
spaces.push(' ', ' ');
}
if (
!selectedGroup &&
group.iconId === ExtensionGroupIconId &&
ExtensionGroupNames.has(group.title)
) {
selectedGroup = group;
}
allGroups.push({
id: group.id,
fileId: file.id,
spaces,
title: group.title,
selected: group.id === selectedGroup?.id
});
});
}
if (!selectedGroup) {
allGroups.splice(1, 0, {
id: '',
fileId: files[0].id,
spaces: [' ', ' '],
title: `${DefaultExtensionGroupName} (${Locale.extensionSaveEntryNewGroup})`,
selected: true
});
}
const saveEntryView = new ExtensionSaveEntryView({
url: payload.url,
user: payload.login,
askSave: RuntimeDataModel.extensionSaveConfig?.askSave || 'always',
allGroups
});
await alertWithTimeout({
header: Locale.extensionSaveEntryHeader,
icon: 'plus',
buttons: [Alerts.buttons.allow, Alerts.buttons.deny],
view: saveEntryView
});
const config = { ...saveEntryView.config };
if (config.groupId) {
const file = files.find((f) => f.id === config.fileId);
selectedGroup = file.getGroup(config.groupId);
} else {
selectedGroup = appModel.createNewGroupWithName(
files[0].groups[0],
files[0],
DefaultExtensionGroupName
);
selectedGroup.setIcon(ExtensionGroupIconId);
config.groupId = selectedGroup.id;
}
RuntimeDataModel.extensionSaveConfig = config;
client.permissions.askSave = config.askSave;
client.permissions.saveTo = { fileId: config.fileId, groupId: config.groupId };
}
appModel.createNewEntryWithFields(selectedGroup, {
Title: url.hostname,
UserName: payload.login,
Password: kdbxweb.ProtectedValue.fromString(payload.password || ''),
URL: payload.url
});
client.stats.passwordsWritten++;
Events.emit('browser-extension-sessions-changed');
Events.emit('refresh');
return encryptResponse(request, {
success: 'true',
version: getVersion(request),
count: null,
entries: null,
hash: KeeWebHash
});
},
async 'get-database-groups'(request) {
@ -487,7 +604,7 @@ const ProtocolHandlers = {
await alertWithTimeout({
header: Locale.extensionNewGroupHeader,
icon: 'folder',
icon: 'folder-plus',
buttons: [Alerts.buttons.allow, Alerts.buttons.deny],
view: createGroupView
});
@ -573,6 +690,9 @@ const ProtocolImpl = {
throw new Error(`Handler not found: ${request.action}`);
}
result = await handler(request, connectionInfo);
if (!result) {
throw new Error(`Handler returned an empty result: ${request.action}`);
}
} catch (e) {
if (!e.code) {
logger.error(`Error in handler ${request.action}`, e);

View File

@ -789,8 +789,12 @@
"extensionConnectAskSaveAuto": "when it's not possible to save automatically",
"extensionConnectSettingsAreForSession": "Settings you select here are valid only for the active session. You can view and manage sessions in KeeWeb settings.",
"extensionUnlockMessage": "Unlock to connect a browser extension",
"extensionNewGroupHeader": "Create a new group?",
"extensionNewGroupHeader": "New group",
"extensionNewGroupBody": "A browser extension is trying to create a new group. Allow this?",
"extensionNewGroupPath": "Group path",
"extensionNewGroupFile": "This group will be created in:"
"extensionNewGroupFile": "This group will be created in:",
"extensionSaveEntryHeader": "Save password",
"extensionSaveEntryBody": "A browser extension is trying to save a password. Allow this?",
"extensionSaveEntryAuto": "Save other passwords automatically in this session",
"extensionSaveEntryNewGroup": "new group"
}

View File

@ -450,7 +450,7 @@ class AppModel {
createNewEntry(args) {
const sel = this.getFirstSelectedGroupForCreation();
if (args && args.template) {
if (args?.template) {
if (sel.file !== args.template.file) {
sel.file = args.template.file;
sel.group = args.template.file.groups[0];
@ -466,6 +466,10 @@ class AppModel {
}
}
createNewEntryWithFields(group, fields) {
return EntryModel.newEntryWithFields(group, fields);
}
createNewGroup() {
const sel = this.getFirstSelectedGroupForCreation();
return GroupModel.newGroup(sel.group, sel.file);

View File

@ -676,6 +676,14 @@ class EntryModel extends Model {
file.setModified();
return model;
}
static newEntryWithFields(group, fields) {
const entry = EntryModel.newEntry(group, group.file);
for (const [field, value] of Object.entries(fields)) {
entry.setField(field, value);
}
return entry;
}
}
EntryModel.defineModelProperties({}, { extensions: true });

View File

@ -0,0 +1,38 @@
import { View } from 'framework/views/view';
import template from 'templates/extension/extension-save-entry.hbs';
class ExtensionSaveEntryView extends View {
template = template;
events = {
'change #extension-create-entry__auto': 'autoChanged',
'change #extension-create-entry__group': 'groupChanged'
};
constructor(model) {
super(model);
const selectedGroup = model.allGroups.find((g) => g.selected);
this.config = {
askSave: model.askSave || 'always',
groupId: selectedGroup.id,
fileId: selectedGroup.fileId
};
}
render() {
super.render(this.model);
}
autoChanged(e) {
this.config.askSave = e.target.checked ? 'auto' : 'always';
}
groupChanged(e) {
const option = e.target.options[e.target.selectedIndex];
this.config.groupId = option.value;
this.config.fileId = option.dataset.file;
}
}
export { ExtensionSaveEntryView };

View File

@ -203,3 +203,4 @@ $fa-var-titlebar-restore: next-fa-glyph();
$fa-var-window-maximize: next-fa-glyph();
$fa-var-download: next-fa-glyph();
$fa-var-exchange-alt: next-fa-glyph();
$fa-var-folder-plus: next-fa-glyph();

View File

@ -0,0 +1,28 @@
<div class="extension-create-entry">
<p>{{res 'extensionSaveEntryBody'}}</p>
<p>
<label for="extension-create-entry__url">{{Res 'website'}}:</label>
<span id="extension-create-entry__url">{{url}}</span>
</p>
<p>
<label for="extension-create-entry__user">{{Res 'user'}}:</label>
<span id="extension-create-entry__user">{{user}}</span>
</p>
<p>
<label for="extension-create-entry__group">{{Res 'group'}}:</label>
<select id="extension-create-entry__group" class="input-base">
{{#each allGroups as |group|}}
<option value="{{group.id}}" data-file="{{group.fileId}}"
{{#if group.selected}}selected{{/if}}
>
{{#if spaces}}{{#each spaces}}&nbsp;{{/each}}{{/if}}
{{group.title}}
</option>
{{/each}}
</select>
</p>
<p>
<input type="checkbox" id="extension-create-entry__auto" {{#ifeq askSave 'auto'}}checked{{/ifeq}} />
<label for="extension-create-entry__auto">{{res 'extensionSaveEntryAuto'}}</label>
</p>
</div>