Skip to content
This repository has been archived by the owner on Jun 15, 2022. It is now read-only.

Commit

Permalink
Privileges
Browse files Browse the repository at this point in the history
  • Loading branch information
moughxyz committed Jan 3, 2019
1 parent 8c4f5a9 commit ee573a8
Show file tree
Hide file tree
Showing 17 changed files with 322 additions and 127 deletions.
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -28,7 +28,7 @@
"react-navigation-header-buttons": "^2.1.1",
"regenerator": "^0.13.3",
"sn-models": "0.1.8",
"standard-file-js": "0.3.19"
"standard-file-js": "0.3.24"
},
"devDependencies": {
"babel-jest": "^23.6.0",
Expand Down
3 changes: 3 additions & 0 deletions src/app.js
Expand Up @@ -9,6 +9,7 @@ import Icons from '@Style/Icons';
import ApplicationState from "@Lib/ApplicationState"
import Auth from './lib/sfjs/authManager'
import ModelManager from './lib/sfjs/modelManager'
import PrivilegesManager from '@SFJS/privilegesManager'
import Sync from './lib/sfjs/syncManager'
import Storage from './lib/sfjs/storageManager'
import ReviewManager from './lib/reviewManager';
Expand Down Expand Up @@ -123,6 +124,8 @@ export default class App extends Component {
// Initialize iOS review manager. Will automatically handle requesting review logic.
ReviewManager.initialize();

PrivilegesManager.get().loadPrivileges();

// Listen to sign out event
Auth.get().addEventHandler((event) => {
if(event == SFAuthManager.DidSignOutEvent) {
Expand Down
4 changes: 3 additions & 1 deletion src/global.js
Expand Up @@ -22,7 +22,8 @@ import {
SFAlertManager,
SFStorageManager,
SFHttpManager,
SFAuthManager
SFAuthManager,
SFPrivilegesManager
} from 'standard-file-js';

SFItem.AppDomain = "org.standardnotes.sn";
Expand All @@ -35,6 +36,7 @@ global.SFAlertManager = SFAlertManager;
global.SFStorageManager = SFStorageManager;
global.SFHttpManager = SFHttpManager;
global.SFAuthManager = SFAuthManager;
global.SFPrivilegesManager = SFPrivilegesManager;

import SF from "./lib/sfjs/sfjs"
global.SFJS = SF.get();
Expand Down
10 changes: 10 additions & 0 deletions src/lib/itemActionManager.js
Expand Up @@ -17,6 +17,9 @@ export default class ItemActionManager {
static LockEvent = "LockEvent";
static UnlockEvent = "UnlockEvent";

static ProtectEvent = "ProtectEvent";
static UnprotectEvent = "UnprotectEvent";

static ShareEvent = "ShareEvent";

/* The afterConfirmCallback is called after user confirms deletion pop up */
Expand Down Expand Up @@ -66,6 +69,13 @@ export default class ItemActionManager {
callback && callback();
}

else if(event == this.ProtectEvent || event == this.UnprotectEvent) {
item.content.protected = !item.content.protected;
item.setDirty(true);
Sync.get().sync();
callback && callback();
}

else if(event == this.ShareEvent) {
ApplicationState.get().performActionWithoutStateChangeImpact(() => {
Share.share({
Expand Down
4 changes: 2 additions & 2 deletions src/lib/keysManager.js
Expand Up @@ -295,9 +295,9 @@ export default class KeysManager {

// Local Security

async clearOfflineKeysAndData() {
async clearOfflineKeysAndData(force = false) {
// make sure user is authenticated before performing this step
if(!this.offlineKeys.mk) {
if(!this.offlineKeys.mk && !force) {
alert("Unable to remove passcode. Make sure you are properly authenticated and try again.");
return false;
}
Expand Down
7 changes: 7 additions & 0 deletions src/lib/sfjs/authManager.js
Expand Up @@ -66,4 +66,11 @@ export default class Auth extends SFAuthManager {
return null;
}
}

async verifyAccountPassword(password) {
let authParams = await this.getAuthParams();
let keys = await SFJS.crypto.computeEncryptionKeysForUser(password, authParams);
let success = keys.mk === (await this.keys()).mk;
return success;
}
}
7 changes: 3 additions & 4 deletions src/lib/sfjs/modelManager.js
@@ -1,13 +1,14 @@
import Storage from "./storageManager"
import "../../models/extend/item.js";
import { SFPredicate } from "standard-file-js";
import { SFPredicate, SFPrivileges } from "standard-file-js";

SFModelManager.ContentTypeClassMapping = {
"Note" : SNNote,
"Tag" : SNTag,
"SN|SmartTag": SNSmartTag,
"SN|Theme" : SNTheme,
"SN|Component" : SNComponent
"SN|Component" : SNComponent,
"SN|Privileges" : SFPrivileges
};

const SystemSmartTagIdAllNotes = "all-notes";
Expand All @@ -32,8 +33,6 @@ export default class ModelManager extends SFModelManager {
this.tags = [];
this.themes = [];

this.acceptableContentTypes = ["Note", "Tag", "SN|SmartTag", "SN|Theme", "SN|Component"];

this.buildSystemSmartTags();
}

Expand Down
102 changes: 102 additions & 0 deletions src/lib/sfjs/privilegesManager.js
@@ -0,0 +1,102 @@
import ModelManager from "./modelManager";
import Sync from "./syncManager";
import { SFPrivilegesManager, SFSingletonManager } from "standard-file-js";
import AuthenticationSourceAccountPassword from "@Screens/Authentication/Sources/AuthenticationSourceAccountPassword";
import AuthenticationSourceLocalPasscode from "@Screens/Authentication/Sources/AuthenticationSourceLocalPasscode";
import AuthenticationSourceFingerprint from "@Screens/Authentication/Sources/AuthenticationSourceFingerprint";
import KeysManager from "@Lib/keysManager"
import Storage from "@SFJS/storageManager"
import Auth from "@SFJS/authManager"

export default class PrivilegesManager extends SFPrivilegesManager {

static instance = null;

static get() {
if (this.instance == null) {
let singletonManager = new SFSingletonManager(ModelManager.get(), Sync.get());
this.instance = new PrivilegesManager(ModelManager.get(), Sync.get(), singletonManager);
}

return this.instance;
}

constructor(modelManager, syncManager, singletonManager) {
super(modelManager, syncManager, singletonManager);

this.setDelegate({
isOffline: async () => {
return Auth.get().offline();
},
hasLocalPasscode: async () => {
return KeysManager.get().hasOfflinePasscode();
},
saveToStorage: async (key, value) => {
return Storage.get().setItem(key, value);
},
getFromStorage: async (key) => {
return Storage.get().getItem(key);
}
});
}

async presentPrivilegesModal(action, navigation, onSuccess, onCancel) {
if(this.authenticationInProgress()) {
onCancel && onCancel();
return;
}

let customSuccess = () => {
onSuccess && onSuccess();
this.authInProgress = false;
}

let customCancel = () => {
onCancel && onCancel();
this.authInProgress = false;
}

let sources = await this.sourcesForAction(action);

navigation.navigate("Authenticate", {
authenticationSources: sources,
hasCancelOption: true,
onSuccess: () => {
customSuccess();
},
onCancel: () => {
customCancel();
}
});

this.authInProgress = true;
}

authenticationInProgress() {
return this.authInProgress;
}

async sourcesForAction(action) {
const sourcesForCredential = (credential) => {
if(credential == SFPrivilegesManager.CredentialAccountPassword) {
return [new AuthenticationSourceAccountPassword()];
} else if(credential == SFPrivilegesManager.CredentialLocalPasscode) {
var hasPasscode = KeysManager.get().hasOfflinePasscode();
var hasFingerprint = KeysManager.get().hasFingerprint();
let sources = [];
if(hasPasscode) {sources.push(new AuthenticationSourceLocalPasscode());}
if(hasFingerprint) {sources.push(new AuthenticationSourceFingerprint());}
return sources;
}
}

let credentials = await this.netCredentialsForAction(action);
let sources = [];
for(var credential of credentials) {
sources = sources.concat(sourcesForCredential(credential));
}

return sources;
}

}
10 changes: 0 additions & 10 deletions src/models/extend/item.js
Expand Up @@ -21,16 +21,6 @@ SFItem.prototype.dateToLocalizedString = function(date) {
return moment(date).format('llll');
}

// Define these new methods

SFItem.prototype.initUUID = async function() {
if(!this.uuid) {
return SFJS.crypto.generateUUID().then((uuid) => {
this.uuid = uuid;
})
}
}

// Define these getters

Object.defineProperty(SFItem.prototype, "key", {
Expand Down
16 changes: 16 additions & 0 deletions src/screens/Abstract.js
Expand Up @@ -6,6 +6,7 @@ import HeaderTitleView from "../components/HeaderTitleView"
import HeaderButtons, { HeaderButton, Item } from 'react-navigation-header-buttons';
import Icon from 'react-native-vector-icons/Ionicons';
import ThemedComponent from "@Components/ThemedComponent";
import PrivilegesManager from "@SFJS/privilegesManager"

const IoniconsHeaderButton = passMeFurther => (
// the `passMeFurther` variable here contains props from <Item .../> as well as <HeaderButtons ... />
Expand Down Expand Up @@ -214,6 +215,21 @@ export default class Abstract extends ThemedComponent {
this.props.navigation.goBack(null);
}

async handlePrivilegedAction(isProtected, action, run) {
if(isProtected) {
let actionRequiresPrivs = await PrivilegesManager.get().actionRequiresPrivilege(action);
if(actionRequiresPrivs) {
PrivilegesManager.get().presentPrivilegesModal(action, this.props.navigation, () => {
run();
});
} else {
run();
}
} else {
run();
}
}

static IsShallowEqual = (newObj, prevObj, keys) => {
for(var key of keys) {
if(newObj[key] !== prevObj[key]) {
Expand Down
42 changes: 35 additions & 7 deletions src/screens/Authentication/Authenticate.js
Expand Up @@ -10,13 +10,19 @@ import SectionedAccessoryTableCell from "@Components/SectionedAccessoryTableCell
import SectionedOptionsTableCell from "@Components/SectionedOptionsTableCell";
import StyleKit from "@Style/StyleKit"
import Icon from 'react-native-vector-icons/Ionicons';
import KeysManager from "@Lib/keysManager";

// Dev mode only. Used to destroy data
// import KeysManager from "@Lib/keysManager";
// import Auth from "@SFJS/authManager"

export default class Authenticate extends Abstract {

static navigationOptions = ({ navigation, navigationOptions }) => {
let templateOptions = {
title: "Authenticate",
rightButton: {
title: "Submit",
}
}
return Abstract.getDefaultNavigationOptions({navigation, navigationOptions, templateOptions});
};
Expand All @@ -30,13 +36,25 @@ export default class Authenticate extends Abstract {
}
}

if(__DEV__) {
// if(__DEV__) {
// props.navigation.setParams({
// leftButton: {
// title: "Destroy Data",
// onPress: () => {
// Auth.get().signout();
// KeysManager.get().clearOfflineKeysAndData(true);
// }
// }
// })
// }

if(this.getProp("hasCancelOption")) {
props.navigation.setParams({
leftButton: {
title: "Clear",
title: "Cancel",
onPress: () => {
KeysManager.get().clearAccountKeysAndData();
KeysManager.get().clearOfflineKeysAndData();
this.getProp("onCancel")();
this.dismiss();
}
}
})
Expand Down Expand Up @@ -113,10 +131,20 @@ export default class Authenticate extends Abstract {
}

onSuccess() {
this.getProp("onSuccess")();
// Wait for componentWillBlur to call onSuccess callback.
// This way, if the callback has another route change, the dismissal
// of this one won't affect it.
this.successful = true;
this.dismiss();
}

componentWillBlur() {
super.componentWillBlur();
if(this.successful) {
this.getProp("onSuccess")();
}
}

inputTextChanged(text, source) {
source.setAuthenticationValue(text);
this.forceUpdate();
Expand Down Expand Up @@ -164,7 +192,7 @@ export default class Authenticate extends Abstract {
<View key={source.identifier}>
<SectionHeader
title={source.title + (source.status == "waiting-turn" ? " — Waiting" : "")}
subtitle={hasHeaderSubtitle && source.label}
subtitle={hasHeaderSubtitle && source.label}
tinted={source == this.state.activeSource}
/>
{source.type == "input" &&
Expand Down
@@ -1,5 +1,6 @@
import SF from '@SFJS/sfjs'
import Storage from '@SFJS/storageManager'
import Auth from '@SFJS/authManager'
import KeysManager from '@Lib/keysManager'
import AuthenticationSource from "./AuthenticationSource"

Expand Down Expand Up @@ -41,8 +42,12 @@ export default class AuthenticationSourceAccountPassword extends AuthenticationS
async authenticate() {
this.didBegin();

// TODO
return this._success();
let success = await Auth.get().verifyAccountPassword(this.authenticationValue);
if(success) {
return this._success();
} else {
return this._fail("Invalid account password. Please try again.");
}
}

_success() {
Expand Down

0 comments on commit ee573a8

Please sign in to comment.