Privileges Management UI

This commit is contained in:
Mo Bitar
2019-01-08 15:47:49 -06:00
parent 7b80aa6cc6
commit bb33691dea
6 changed files with 217 additions and 8 deletions

View File

@@ -19,6 +19,7 @@ import MainSideMenu from "@SideMenu/MainSideMenu"
import NoteSideMenu from "@SideMenu/NoteSideMenu"
import Settings from "@Screens/Settings/Settings"
import InputModal from "@Screens/InputModal"
import ManagePrivileges from "@Screens/ManagePrivileges"
import Authenticate from "@Screens/Authentication/Authenticate"
import SideMenuManager from "@SideMenu/SideMenuManager"
@@ -76,12 +77,16 @@ const AuthenticateModalStack = createStackNavigator({
Screen1: Authenticate
})
const ManagePrivilegesStack = createStackNavigator({
Screen1: ManagePrivileges
})
const AppDrawer = createStackNavigator({
Home: AppDrawerStack,
Settings: SettingsStack,
InputModal: InputModalStack,
Authenticate: AuthenticateModalStack,
ManagePrivileges: ManagePrivilegesStack
}, {
mode: "modal",
headerMode: 'none',

View File

@@ -59,6 +59,9 @@ export default class PrivilegesManager extends SFPrivilegesManager {
let sources = await this.sourcesForAction(action);
let sessionLengthOptions = await this.getSessionLengthOptions();
let selectedSessionLength = await this.getSelectedSessionLength();
navigation.navigate("Authenticate", {
leftButton: {
title: ApplicationState.isIOS ? "Cancel" : null,
@@ -66,7 +69,10 @@ export default class PrivilegesManager extends SFPrivilegesManager {
},
authenticationSources: sources,
hasCancelOption: true,
onSuccess: () => {
sessionLengthOptions: sessionLengthOptions,
selectedSessionLength: selectedSessionLength,
onSuccess: (selectedSessionLength) => {
this.setSessionLength(selectedSessionLength);
customSuccess();
},
onCancel: () => {

View File

@@ -35,6 +35,8 @@ export default class Authenticate extends Abstract {
source.initializeForInterface();
}
this._sessionLength = this.getProp("selectedSessionLength");
// if(__DEV__) {
// props.navigation.setParams({
// leftButton: {
@@ -131,7 +133,7 @@ export default class Authenticate extends Abstract {
componentWillBlur() {
super.componentWillBlur();
if(this.successful) {
this.getProp("onSuccess")();
this.getProp("onSuccess")(this._sessionLength);
}
}
@@ -140,6 +142,15 @@ export default class Authenticate extends Abstract {
this.forceUpdate();
}
get sessionLengthOptions() {
return this.getProp("sessionLengthOptions");
}
setSessionLength(length) {
this._sessionLength = length;
this.forceUpdate();
}
_renderAuthenticationSoure = (source, index) => {
let isLast = index == this.sources.length - 1;
@@ -215,6 +226,22 @@ export default class Authenticate extends Abstract {
onPress={() => this.submitPressed()}
/>
{this.sessionLengthOptions && this.sessionLengthOptions.length > 0 &&
<View style={this.styles.rememberForSection}>
<SectionHeader title={"Remember For"} />
{this.sessionLengthOptions.map((option, index) =>
<SectionedAccessoryTableCell
text={option.label}
key={`${index}`}
first={index == 0}
last={index == this.sessionLengthOptions.length - 1}
selected={() => {return option.value == this._sessionLength}}
onPress={() => {this.setSessionLength(option.value)}}
/>
)}
</View>
}
</ScrollView>
</View>
)
@@ -222,12 +249,11 @@ export default class Authenticate extends Abstract {
loadStyles() {
this.styles = {
authSourceSection: {
},
authSourceSectionNotLast: {
marginBottom: 10
},
submitButtonCell: {
rememberForSection: {
marginTop: 10
}
}
}

View File

@@ -0,0 +1,169 @@
import React, { Component } from 'react';
import { TextInput, View, Text, Platform, SafeAreaView, ScrollView } from 'react-native';
import StyleKit from "@Style/StyleKit"
import TableSection from "@Components/TableSection";
import SectionedTableCell from "@Components/SectionedTableCell";
import SectionedOptionsTableCell from "@Components/SectionedOptionsTableCell";
import SectionedAccessoryTableCell from "@Components/SectionedAccessoryTableCell"
import SectionHeader from "@Components/SectionHeader";
import ButtonCell from "@Components/ButtonCell";
import Abstract from "@Screens/Abstract"
import LockedView from "@Containers/LockedView";
import ApplicationState from "@Lib/ApplicationState"
import Auth from "@SFJS/authManager"
import KeysManager from "@Lib/keysManager"
import PrivilegesManager from "@SFJS/privilegesManager"
export default class ManagePrivileges extends Abstract {
static navigationOptions = ({ navigation, navigationOptions }) => {
let templateOptions = {
title: "Privileges",
leftButton: {
title: ApplicationState.isIOS ? "Done" : null,
iconName: ApplicationState.isIOS ? null : StyleKit.nameForIcon("checkmark"),
}
}
return Abstract.getDefaultNavigationOptions({navigation, navigationOptions, templateOptions});
};
constructor(props) {
super(props);
this.state = {
availableActions: [],
availableCredentials: []
}
props.navigation.setParams({
leftButton: {
title: ApplicationState.isIOS ? "Done" : null,
iconName: ApplicationState.isIOS ? null : StyleKit.nameForIcon("checkmark"),
onPress: () => {
this.dismiss();
}
}
})
this.hasPasscode = KeysManager.get().hasOfflinePasscode();
this.hasAccount = !Auth.get().offline();
this.reloadPrivileges();
}
displayInfoForCredential(credential) {
return PrivilegesManager.get().displayInfoForCredential(credential).label;
}
displayInfoForAction(action) {
return PrivilegesManager.get().displayInfoForAction(action).label;
}
isCredentialRequiredForAction(action, credential) {
if(!this.privileges) {
return false;
}
return this.privileges.isCredentialRequiredForAction(action, credential);
}
clearSession = () => {
PrivilegesManager.get().clearSession().then(() => {
this.reloadPrivileges();
})
}
async reloadPrivileges() {
this.privileges = await PrivilegesManager.get().getPrivileges();
let availableCredentials = PrivilegesManager.get().getAvailableCredentials();
let availableActions = PrivilegesManager.get().getAvailableActions();
this.credentialDisplayInfo = {};
for(let cred of availableCredentials) {
this.credentialDisplayInfo[cred] = this.displayInfoForCredential(cred);
}
let sessionEndDate = await PrivilegesManager.get().getSessionExpirey();
this.setState({
availableActions: availableActions,
availableCredentials: availableCredentials,
sessionExpirey: sessionEndDate.toLocaleString(),
sessionExpired: new Date() >= sessionEndDate
});
}
valueChanged(action, credential) {
this.privileges.toggleCredentialForAction(action, credential);
PrivilegesManager.get().savePrivileges();
this.forceUpdate();
}
render() {
if(this.state.lockContent) {
return (<LockedView />);
}
return (
<View style={StyleKit.styles.container}>
<ScrollView>
{this.state.sessionExpirey && !this.state.sessionExpired &&
<View style={this.styles.section}>
<SectionHeader title={"Current Session"} />
<SectionedTableCell first={true}>
<Text style={[this.styles.cellText]}>
You will not be asked to authenticate until {this.state.sessionExpirey}.
</Text>
</SectionedTableCell>
<ButtonCell last={true} leftAligned={true} title={`Clear Local Session`} onPress={this.clearSession} />
</View>
}
{this.state.availableActions.map((action, actionIndex) =>
<View style={this.styles.section} key={`${actionIndex}`}>
<SectionHeader title={this.displayInfoForAction(action)} />
{this.state.availableCredentials.map((credential, credIndex) =>
<SectionedAccessoryTableCell
text={this.displayInfoForCredential(credential)}
key={`${actionIndex}+${credIndex}`}
first={credIndex == 0}
last={credIndex == this.state.availableCredentials.length - 1}
selected={() => {return this.isCredentialRequiredForAction(action, credential)}}
onPress={() => {this.valueChanged(action, credential)}}
/>
)}
</View>
)}
<View style={this.styles.section}>
<SectionHeader title={"About Privileges"} />
<SectionedTableCell first={true} last={true}>
<Text style={[this.styles.aboutText, this.styles.cellText]}>
Privileges represent interface level authentication for accessing certain items and features. Privileges are meant to protect against unwanted access in the event of an unlocked application, but do not affect data encryption state.
</Text>
<Text style={[this.styles.aboutText, this.styles.cellText]}>
Privileges sync across your other deviceshowever, note that if you require a "Local Passcode" privilege, and another device does not have a local passcode set up, the local passcode requirement will be ignored on that device.
</Text>
</SectionedTableCell>
</View>
</ScrollView>
</View>
);
}
loadStyles() {
this.styles = {
section: {
marginBottom: 8
},
cellText: {
lineHeight: 19,
fontSize: 16,
color: StyleKit.variables.stylekitForegroundColor,
},
aboutText: {
marginBottom: 8
}
}
}
}

View File

@@ -95,13 +95,15 @@ class OptionsSection extends Abstract {
<SectionHeader title={this.props.title} />
<ButtonCell first={true} leftAligned={true} title={`Manage Privileges`} onPress={this.props.onManagePrivileges} />
{signedIn &&
<ButtonCell first={true} leftAligned={true} title={`Sign out (${this.state.email})`} onPress={this.props.onSignOutPress} />
<ButtonCell leftAligned={true} title={`Sign out (${this.state.email})`} onPress={this.props.onSignOutPress} />
}
<SectionedOptionsTableCell
last={!hasLastExportSection}
first={!signedIn}
first={false}
disabled={this.state.loadingExport}
leftAligned={true}
options={this.exportOptions()}

View File

@@ -250,6 +250,7 @@ export default class Settings extends Abstract {
<OptionsSection
title={"Options"}
onSignOutPress={this.onSignOutPress}
onManagePrivileges={() => {this.props.navigation.navigate("ManagePrivileges")}}
/>
<TableSection>