Skip to content

Commit

Permalink
added ability to revert (#21)
Browse files Browse the repository at this point in the history
  • Loading branch information
undergroundwires committed Jul 19, 2020
1 parent 5702898 commit 9c063d5
Show file tree
Hide file tree
Showing 58 changed files with 1,444 additions and 261 deletions.
46 changes: 46 additions & 0 deletions CONTRIBUTING.md
@@ -0,0 +1,46 @@
# Contributing

- Love your input! Contributing to this project should be as easy and transparent as possible, whether it's:
- Reporting a bug
- Discussing the current state of the code
- Submitting a fix
- Proposing new features
- Becoming a maintainer

## Pull Request Process

- [GitHub flow](https://guides.github.com/introduction/flow/index.html) is used
- Your pull requests are actively welcomed.
- The steps:
1. Fork the repo and create your branch from master.
2. If you've added code that should be tested, add tests.
3. If you've changed APIs, update the documentation.
4. Ensure the test suite passes.
5. Make sure your code lints.
6. Issue that pull request!
- 🙏 DO
- Document your changes in the pull request
- 💡 Check [developer notes](./docs/developer-notes.md) if you need help
- ❗ DON'T
- Do not update the versions, current version is only [set by the maintainer](./docs/gitops.png) and updated automatically by [bump-everywhere](https://github.com/undergroundwires/bump-everywhere)

## Guidelines

### Extend scripts

- Create a [pull request](./../CONTRIBUTING.md#Pull+Request+Process) for [application.yaml](./../src/application/application.yaml)
- 🙏 For any new script, try to add `revertCode` that'll revert the changes caused by the script.
- See [typings](./../src/application/application.yaml.d.ts) for documentation as code.

### Handle the state in presentation layer

- There are two types of components:
- **Stateless**, extends `Vue`
- **Stateful**, extends [`StatefulVue`](./src/presentation/StatefulVue.ts)
- The source of truth for the state lies in [application layer](./src/application/state) and must be updated from the views if they're mutating the state
- They mutate or/and reacts to changes in [application state](./src/application/state).
- You can react by getting the state and listening to it and update the view accordingly in [`mounted()`](https://vuejs.org/v2/api/#mounted) method.

## License

By contributing, you agree that your contributions will be licensed under its GNU General Public License v3.0.
8 changes: 5 additions & 3 deletions README.md
Expand Up @@ -2,7 +2,7 @@

> Web tool to enforce privacy & security best-practices on Windows, because privacy is sexy 🍑🍆
[![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](https://github.com/undergroundwires/privacy.sexy/issues)
[![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](./CONTRIBUTING.md)
[![Language grade: JavaScript](https://img.shields.io/lgtm/grade/javascript/g/undergroundwires/privacy.sexy.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/undergroundwires/privacy.sexy/context:javascript)
[![Maintainability](https://api.codeclimate.com/v1/badges/3a70b7ef602e2264342c/maintainability)](https://codeclimate.com/github/undergroundwires/privacy.sexy/maintainability)
[![Tests status](https://github.com/undergroundwires/privacy.sexy/workflows/Test/badge.svg)](https://github.com/undergroundwires/privacy.sexy/actions)
Expand All @@ -16,15 +16,17 @@

## Why

- You don't need to run any compiled software on your system, just run the generated scripts.
- You don't need to run any compiled software that has access to your system, just run the generated scripts.
- It's open source, both application & infrastructure is 100% transparent
- Fully automated C/CD pipeline to AWS for provisioning serverless infrastructure using GitHub actions.
- Have full visibility into what the tweaks do as you enable them.
- Ability to revert applied scripts
- Easily extendable

## Extend scripts

Fork it & add more scripts in [application.yaml](src/application/application.yaml) and send a pull request 👌
- Fork it & add more scripts in [application.yaml](src/application/application.yaml) and send a pull request 👌
- 📖 More: [extend scripts | CONTRIBUTING.md](./CONTRIBUTING.md#extend-scripts)

## Commands

Expand Down
2 changes: 1 addition & 1 deletion docs/gitops.drawio

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/App.vue
Expand Up @@ -13,7 +13,7 @@

<script lang="ts">
import { Component, Vue, Prop } from 'vue-property-decorator';
import { ApplicationState, IApplicationState } from '@/application/State/ApplicationState';
import { ApplicationState } from '@/application/State/ApplicationState';
import TheHeader from '@/presentation/TheHeader.vue';
import TheFooter from '@/presentation/TheFooter.vue';
import TheCodeArea from '@/presentation/TheCodeArea.vue';
Expand Down
20 changes: 14 additions & 6 deletions src/application/Parser/ApplicationParser.ts
@@ -1,13 +1,12 @@
import { Category } from '../../domain/Category';
import { Application } from '../../domain/Application';
import { Category } from '@/domain/Category';
import { Application } from '@/domain/Application';
import { IApplication } from '@/domain/IApplication';
import { ApplicationYaml } from 'js-yaml-loader!./../application.yaml';
import { parseCategory } from './CategoryParser';

export function parseApplication(content: ApplicationYaml): Application {
export function parseApplication(content: ApplicationYaml): IApplication {
validate(content);
const categories = new Array<Category>();
if (!content.actions || content.actions.length <= 0) {
throw new Error('Application does not define any action');
}
for (const action of content.actions) {
const category = parseCategory(action);
categories.push(category);
Expand All @@ -19,3 +18,12 @@ export function parseApplication(content: ApplicationYaml): Application {
categories);
return app;
}

function validate(content: ApplicationYaml): void {
if (!content) {
throw new Error('application is null or undefined');
}
if (!content.actions || content.actions.length <= 0) {
throw new Error('application does not define any action');
}
}
27 changes: 16 additions & 11 deletions src/application/Parser/CategoryParser.ts
@@ -1,20 +1,18 @@
import { YamlCategory, YamlScript } from 'js-yaml-loader!./application.yaml';
import { Script } from '@/domain/Script';
import { Category } from '../../domain/Category';
import { Category } from '@/domain/Category';
import { parseDocUrls } from './DocumentationParser';
import { parseScript } from './ScriptParser';

let categoryIdCounter: number = 0;


interface ICategoryChildren {
subCategories: Category[];
subScripts: Script[];
}

export function parseCategory(category: YamlCategory): Category {
if (!category.children || category.children.length <= 0) {
throw Error('Category has no children');
}
ensureValid(category);
const children: ICategoryChildren = {
subCategories: new Array<Category>(),
subScripts: new Array<Script>(),
Expand All @@ -31,26 +29,33 @@ export function parseCategory(category: YamlCategory): Category {
);
}

function ensureValid(category: YamlCategory) {
if (!category) {
throw Error('category is null or undefined');
}
if (!category.children || category.children.length === 0) {
throw Error('category has no children');
}
if (!category.category || category.category.length === 0) {
throw Error('category has no name');
}
}

function parseCategoryChild(
categoryOrScript: any, children: ICategoryChildren, parent: YamlCategory) {
if (isCategory(categoryOrScript)) {
const subCategory = parseCategory(categoryOrScript as YamlCategory);
children.subCategories.push(subCategory);
} else if (isScript(categoryOrScript)) {
const yamlScript = categoryOrScript as YamlScript;
const script = new Script(
/* name */ yamlScript.name,
/* code */ yamlScript.code,
/* docs */ parseDocUrls(yamlScript),
/* is recommended? */ yamlScript.recommend);
const script = parseScript(yamlScript);
children.subScripts.push(script);
} else {
throw new Error(`Child element is neither a category or a script.
Parent: ${parent.category}, element: ${categoryOrScript}`);
}
}


function isScript(categoryOrScript: any): boolean {
return categoryOrScript.code && categoryOrScript.code.length > 0;
}
Expand Down
3 changes: 3 additions & 0 deletions src/application/Parser/DocumentationParser.ts
@@ -1,6 +1,9 @@
import { YamlDocumentable } from 'js-yaml-loader!./application.yaml';

export function parseDocUrls(documentable: YamlDocumentable): ReadonlyArray<string> {
if (!documentable) {
throw new Error('documentable is null or undefined');
}
const docs = documentable.docs;
if (!docs) {
return [];
Expand Down
16 changes: 16 additions & 0 deletions src/application/Parser/ScriptParser.ts
@@ -0,0 +1,16 @@
import { Script } from '@/domain/Script';
import { YamlScript } from 'js-yaml-loader!./application.yaml';
import { parseDocUrls } from './DocumentationParser';

export function parseScript(yamlScript: YamlScript): Script {
if (!yamlScript) {
throw new Error('script is null or undefined');
}
const script = new Script(
/* name */ yamlScript.name,
/* code */ yamlScript.code,
/* revertCode */ yamlScript.revertCode,
/* docs */ parseDocUrls(yamlScript),
/* isRecommended */ yamlScript.recommend);
return script;
}
6 changes: 2 additions & 4 deletions src/application/State/ApplicationState.ts
Expand Up @@ -8,7 +8,7 @@ import { Signal } from '@/infrastructure/Events/Signal';
import { parseApplication } from '../Parser/ApplicationParser';
import { IApplicationState } from './IApplicationState';
import { Script } from '@/domain/Script';
import { Application } from '@/domain/Application';
import { IApplication } from '@/domain/IApplication';
import { IApplicationCode } from './Code/IApplicationCode';
import applicationFile from 'js-yaml-loader!@/application/application.yaml';

Expand All @@ -34,13 +34,11 @@ export class ApplicationState implements IApplicationState {

private constructor(
/** Inner instance of the all scripts */
public readonly app: Application,
public readonly app: IApplication,
/** Initially selected scripts */
public readonly defaultScripts: Script[]) {
this.selection = new UserSelection(app, defaultScripts);
this.code = new ApplicationCode(this.selection, app.version);
this.filter = new UserFilter(app);
}
}

export { IApplicationState, IUserFilter };
13 changes: 8 additions & 5 deletions src/application/State/Code/ApplicationCode.ts
@@ -1,16 +1,19 @@
import { SelectedScript } from '@/application/State/Selection/SelectedScript';
import { IUserSelection } from '@/application/State/Selection/IUserSelection';
import { UserScriptGenerator } from './UserScriptGenerator';
import { IUserSelection } from './../Selection/IUserSelection';
import { Signal } from '@/infrastructure/Events/Signal';
import { IApplicationCode } from './IApplicationCode';
import { IScript } from '@/domain/IScript';
import { IUserScriptGenerator } from './IUserScriptGenerator';

export class ApplicationCode implements IApplicationCode {
public readonly changed = new Signal<string>();
public current: string;

private readonly generator: UserScriptGenerator;
private readonly generator: IUserScriptGenerator = new UserScriptGenerator();

constructor(userSelection: IUserSelection, private readonly version: string) {
constructor(
userSelection: IUserSelection,
private readonly version: string) {
if (!userSelection) { throw new Error('userSelection is null or undefined'); }
if (!version) { throw new Error('version is null or undefined'); }
this.generator = new UserScriptGenerator();
Expand All @@ -20,7 +23,7 @@ export class ApplicationCode implements IApplicationCode {
});
}

private setCode(scripts: ReadonlyArray<IScript>) {
private setCode(scripts: ReadonlyArray<SelectedScript>) {
this.current = scripts.length === 0 ? '' : this.generator.buildCode(scripts, this.version);
this.changed.notify(this.current);
}
Expand Down
5 changes: 5 additions & 0 deletions src/application/State/Code/IUserScriptGenerator.ts
@@ -0,0 +1,5 @@
import { SelectedScript } from '@/application/State/Selection/SelectedScript';

export interface IUserScriptGenerator {
buildCode(selectedScripts: ReadonlyArray<SelectedScript>, version: string): string;
}
19 changes: 11 additions & 8 deletions src/application/State/Code/UserScriptGenerator.ts
@@ -1,7 +1,8 @@
import { SelectedScript } from '@/application/State/Selection/SelectedScript';
import { IUserScriptGenerator } from './IUserScriptGenerator';
import { CodeBuilder } from './CodeBuilder';
import { Script } from '@/domain/Script';

const adminRightsScript = {
export const adminRightsScript = {
name: 'Ensure admin privileges',
code: 'fltmc >nul 2>&1 || (\n' +
' echo This batch script requires administrator privileges. Right-click on\n' +
Expand All @@ -11,17 +12,19 @@ const adminRightsScript = {
')',
};

export class UserScriptGenerator {
public buildCode(scripts: ReadonlyArray<Script>, version: string): string {
if (!scripts) { throw new Error('scripts is undefined'); }
if (!scripts.length) { throw new Error('scripts are empty'); }
export class UserScriptGenerator implements IUserScriptGenerator {
public buildCode(selectedScripts: ReadonlyArray<SelectedScript>, version: string): string {
if (!selectedScripts) { throw new Error('scripts is undefined'); }
if (!selectedScripts.length) { throw new Error('scripts are empty'); }
if (!version) { throw new Error('version is undefined'); }
const builder = new CodeBuilder()
.appendLine('@echo off')
.appendCommentLine(`https://privacy.sexy — v${version}${new Date().toUTCString()}`)
.appendFunction(adminRightsScript.name, adminRightsScript.code).appendLine();
for (const script of scripts) {
builder.appendFunction(script.name, script.code).appendLine();
for (const selection of selectedScripts) {
const name = selection.revert ? `${selection.script.name} (revert)` : selection.script.name;
const code = selection.revert ? selection.script.revertCode : selection.script.code;
builder.appendFunction(name, code).appendLine();
}
return builder.appendLine()
.appendLine('pause')
Expand Down
2 changes: 1 addition & 1 deletion src/application/State/Filter/FilterResult.ts
@@ -1,5 +1,5 @@
import { IFilterResult } from './IFilterResult';
import { IScript } from '@/domain/Script';
import { IScript } from '@/domain/IScript';
import { ICategory } from '@/domain/ICategory';

export class FilterResult implements IFilterResult {
Expand Down
24 changes: 18 additions & 6 deletions src/application/State/Filter/UserFilter.ts
@@ -1,14 +1,15 @@
import { IScript } from '@/domain/IScript';
import { FilterResult } from './FilterResult';
import { IFilterResult } from './IFilterResult';
import { Application } from '../../../domain/Application';
import { IApplication } from '@/domain/IApplication';
import { IUserFilter } from './IUserFilter';
import { Signal } from '@/infrastructure/Events/Signal';

export class UserFilter implements IUserFilter {
public readonly filtered = new Signal<IFilterResult>();
public readonly filterRemoved = new Signal<void>();

constructor(private application: Application) {
constructor(private application: IApplication) {

}

Expand All @@ -18,11 +19,9 @@ export class UserFilter implements IUserFilter {
}
const filterLowercase = filter.toLocaleLowerCase();
const filteredScripts = this.application.getAllScripts().filter(
(script) =>
script.name.toLowerCase().includes(filterLowercase) ||
script.code.toLowerCase().includes(filterLowercase));
(script) => isScriptAMatch(script, filterLowercase));
const filteredCategories = this.application.getAllCategories().filter(
(script) => script.name.toLowerCase().includes(filterLowercase));
(category) => category.name.toLowerCase().includes(filterLowercase));

const matches = new FilterResult(
filteredScripts,
Expand All @@ -37,3 +36,16 @@ export class UserFilter implements IUserFilter {
this.filterRemoved.notify();
}
}

function isScriptAMatch(script: IScript, filterLowercase: string) {
if (script.name.toLowerCase().includes(filterLowercase)) {
return true;
}
if (script.code.toLowerCase().includes(filterLowercase)) {
return true;
}
if (script.revertCode) {
return script.revertCode.toLowerCase().includes(filterLowercase);
}
return false;
}
8 changes: 5 additions & 3 deletions src/application/State/Selection/IUserSelection.ts
@@ -1,11 +1,13 @@
import { SelectedScript } from './SelectedScript';
import { ISignal } from '@/infrastructure/Events/Signal';
import { IScript } from '@/domain/IScript';

export interface IUserSelection {
readonly changed: ISignal<ReadonlyArray<IScript>>;
readonly selectedScripts: ReadonlyArray<IScript>;
readonly changed: ISignal<ReadonlyArray<SelectedScript>>;
readonly selectedScripts: ReadonlyArray<SelectedScript>;
readonly totalSelected: number;
addSelectedScript(scriptId: string): void;
addSelectedScript(scriptId: string, revert: boolean): void;
addOrUpdateSelectedScript(scriptId: string, revert: boolean): void;
removeSelectedScript(scriptId: string): void;
selectOnly(scripts: ReadonlyArray<IScript>): void;
isSelected(script: IScript): boolean;
Expand Down
14 changes: 14 additions & 0 deletions src/application/State/Selection/SelectedScript.ts
@@ -0,0 +1,14 @@
import { BaseEntity } from '@/infrastructure/Entity/BaseEntity';
import { IScript } from '@/domain/IScript';

export class SelectedScript extends BaseEntity<string> {
constructor(
public readonly script: IScript,
public readonly revert: boolean,
) {
super(script.id);
if (revert && !script.canRevert()) {
throw new Error('cannot revert an irreversible script');
}
}
}

0 comments on commit 9c063d5

Please sign in to comment.