Skip to content

Commit

Permalink
Rewrite parser for osx-storage from js to ts and simplify it
Browse files Browse the repository at this point in the history
In previous implementation each entity was separated on lines but
currently we get chunks with all lines for each entity.
I have to extends Transform this way because of:
microsoft/TypeScript#17032
Use _final instead of _flush to avoid an error: stream.push() after EOF
Add dependency injection for osx parse.
  • Loading branch information
jetbrains authored and rugpanov committed Dec 19, 2017
1 parent d1878e7 commit d9fa481
Show file tree
Hide file tree
Showing 7 changed files with 140 additions and 136 deletions.
13 changes: 9 additions & 4 deletions src/bll/credentialsstore/osx/osx-keychain-access.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
"use strict";

import {injectable} from "inversify";
import {inject, injectable} from "inversify";
import {OsxSecurityParsingStream, OsxSecurityParsingStreamWrapper} from "./osx-keychain-parser";
import {TYPES} from "../../utils/constants";
const childProcess = require("child_process");
const es = require("event-stream");
const parser = require("./osx-keychain-parser");

@injectable()
export class OsxKeychain {

private readonly securityPath: string = "/usr/bin/security";
private targetNamePrefix: string = "";
private parser: OsxSecurityParsingStream;

public constructor(@inject(TYPES.OsxSecurityParsingStreamWrapper) wrapper: OsxSecurityParsingStreamWrapper) {
this.parser = wrapper.parser;
}

public setPrefix(prefix: string) {
this.targetNamePrefix = prefix;
Expand All @@ -21,13 +27,12 @@ export class OsxKeychain {

public getCredentialsWithoutPasswordsListStream() {
const securityProcess = childProcess.spawn(this.securityPath, ["dump-keychain"]);

return securityProcess.stdout
.pipe(es.split())
.pipe(es.mapSync(function (line) {
return line.replace(/\\134/g, "\\");
}))
.pipe(new parser.ParsingStream());
.pipe(this.parser);
}

public getPasswordForUser(targetName: string): Promise<string> {
Expand Down
126 changes: 0 additions & 126 deletions src/bll/credentialsstore/osx/osx-keychain-parser.js

This file was deleted.

122 changes: 122 additions & 0 deletions src/bll/credentialsstore/osx/osx-keychain-parser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
"use strict";

const stream = require("readable-stream");
const Transform = stream.Transform;
import {injectable} from "inversify";

@injectable()
export class OsxSecurityParsingStreamWrapper {
private readonly instance: OsxSecurityParsingStream;

constructor() {
this.instance = new OsxSecurityParsingStream();
}

public get parser(): OsxSecurityParsingStream {
return this.instance;
}
}

export class OsxSecurityParsingStream extends (Transform as { new(): any; }) {

private readonly rootFieldMask = /^([^:]+):(?: (?:"([^"]+)")|(.*))?$/;
private readonly propertiesMask = /^ {4}(?:(0x[0-9a-fA-F]+) |"([a-z]{4})")<[^>]+>=(?:(<NULL>)|"([^"]+)"|(0x[0-9a-fA-F]+)(?: {2}"([^"]+)")|(.*)?)/;

private currentKeychain: string;
private hadAttributesString: boolean = false;

public constructor() {
super();
Transform.call(this, {
objectMode: true
});

this.currentEntry = undefined;
}

public _transform(chunk, encoding, callback) {
const chunkData: string = chunk.toString();
if (!chunkData && chunkData === "") {
callback();
return;
}
chunkData.split("\n").forEach((line) => {
this.processNextLine(line);
});

callback();
}

private processNextLine(line: string): void {
if (!this.currentKeychain) {
this.currentKeychain = this.getInitialKeychainValue(line);
if (this.currentKeychain) {
this.currentEntry = {
keychain: this.currentKeychain
};
}
} else {
if (!this.hadAttributesString) {
if (this.isAttributesString(line)) {
this.hadAttributesString = true;
} else {
this.collectMetaInfo(line);
}
} else {
this.collectProperties(line);
}
}
}

private getInitialKeychainValue(line: string): string {
let keychain: string;
let match = this.rootFieldMask.exec(line);
if (match && match[1] === "keychain") {
keychain = match[2];
}
return keychain;
}

private isAttributesString(line: string): boolean {
const match = this.rootFieldMask.exec(line);
return match && match[1] === "attributes";
}

private collectMetaInfo(line: string): void {
let match = this.rootFieldMask.exec(line);
if (match) {
this.currentEntry[match[1]] = match[2];
}
}

private collectProperties(line: string): void {
const match = this.propertiesMask.exec(line);
if (match) {
if (match[2]) {
const value = match[6] || match[4];
if (value) {
this.currentEntry[match[2]] = value;
}
}
} else {
this.finalize(line);
}
}

private finalize(line ?:string): void {
if (this.currentEntry) {
this.push(this.currentEntry);
this.currentEntry = undefined;
this.currentKeychain = undefined;
this.hadAttributesString = false;
if (line) {
this.processNextLine(line);
}
}
}

public _final(callback) {
this.finalize();
callback();
}
}
3 changes: 2 additions & 1 deletion src/bll/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,8 @@ export const TYPES = {
ResourceProvider: Symbol("ChangesProvider"),
BuildProvider: Symbol("BuildProvider"),
LinuxFileApi: Symbol("LinuxFileApi"),
WinCredStoreParsingStreamWrapper: Symbol("WinCredStoreParsingStreamWrapper")
WinCredStoreParsingStreamWrapper: Symbol("WinCredStoreParsingStreamWrapper"),
OsxSecurityParsingStreamWrapper: Symbol("OsxSecurityParsingStreamWrapper"),
};

export enum CvsOperation {
Expand Down
5 changes: 2 additions & 3 deletions src/extensionmanager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,15 @@ import {TeamCityStatusBarItem} from "./view/teamcitystatusbaritem";
import {CredentialsStore} from "./bll/credentialsstore/credentialsstore";
import {NotificationWatcher} from "./bll/notifications/notificationwatcher";
import {
commands,
Disposable,
ExtensionContext,
OutputChannel,
StatusBarAlignment,
StatusBarItem,
workspace,
commands
workspace
} from "vscode";
import {ProviderManager} from "./view/providermanager";
import {PersistentStorageManager} from "./bll/credentialsstore/persistentstoragemanager";

@injectable()
export class ExtensionManager {
Expand Down
2 changes: 2 additions & 0 deletions src/inversify.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import {OsxKeychainApi} from "./bll/credentialsstore/osx/osx-keychain-api";
import {OsxKeychain} from "./bll/credentialsstore/osx/osx-keychain-access";
import {FileTokenStorage} from "./bll/credentialsstore/linux/file-token-storage";
import {WinCredStoreParsingStreamWrapper} from "./bll/credentialsstore/win32/win-credstore-parser";
import {OsxSecurityParsingStreamWrapper} from "./bll/credentialsstore/osx/osx-keychain-parser";

export const myContainer = new Container();
myContainer.bind<Settings>(TYPES.Settings).to(SettingsImpl).inSingletonScope();
Expand Down Expand Up @@ -78,3 +79,4 @@ myContainer.bind<OsxKeychainApi>(TYPES.OsxKeychainApi).to(OsxKeychainApi).inSing
myContainer.bind<OsxKeychain>(TYPES.OsxKeychain).to(OsxKeychain).inSingletonScope();
myContainer.bind<FileTokenStorage>(TYPES.FileTokenStorage).to(FileTokenStorage).inSingletonScope();
myContainer.bind<WinCredStoreParsingStreamWrapper>(TYPES.WinCredStoreParsingStreamWrapper).to(WinCredStoreParsingStreamWrapper).inSingletonScope();
myContainer.bind<OsxSecurityParsingStreamWrapper>(TYPES.OsxSecurityParsingStreamWrapper).to(OsxSecurityParsingStreamWrapper).inSingletonScope();
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import * as path from "path";
import {OsxKeychainApi} from "../../src/bll/credentialsstore/osx/osx-keychain-api";
import {OsxKeychain} from "../../src/bll/credentialsstore/osx/osx-keychain-access";
import {WinCredStoreParsingStreamWrapper} from "../../src/bll/credentialsstore/win32/win-credstore-parser";
import {OsxSecurityParsingStreamWrapper} from "../../src/bll/credentialsstore/osx/osx-keychain-parser";

suite("PersistentStorageManager - integration test", function () {

Expand All @@ -26,8 +27,8 @@ suite("PersistentStorageManager - integration test", function () {
fileTokenStorage.setFilename(path.join(os.homedir(), defaultFolder, defaultFilename));
const linuxFileApi: LinuxFileApi = new LinuxFileApi(fileTokenStorage);
const winStoreApi = new WindowsCredentialStoreApi(winPersistentCredentialsStore);

const osxKeychain: OsxKeychain = new OsxKeychain();
const osxParseWrapper: OsxSecurityParsingStreamWrapper = new OsxSecurityParsingStreamWrapper();
const osxKeychain: OsxKeychain = new OsxKeychain(osxParseWrapper);
osxKeychain.setPrefix(TestSettings.persistentCredentialsPrefix);
const osxKeychainApi: OsxKeychainApi = new OsxKeychainApi(osxKeychain);
const credentialManager: PersistentStorageManager = new PersistentStorageManager(winStoreApi, linuxFileApi, osxKeychainApi, os);
Expand Down

0 comments on commit d9fa481

Please sign in to comment.