From bb33691dea28b3bbf4b72748ebdfa2cb35ef0c5c Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Tue, 8 Jan 2019 15:47:49 -0600 Subject: [PATCH] Privileges Management UI --- src/App.js | 7 +- src/lib/sfjs/privilegesManager.js | 8 +- src/screens/Authentication/Authenticate.js | 34 +++- src/screens/ManagePrivileges.js | 169 ++++++++++++++++++ .../Settings/Sections/OptionsSection.js | 6 +- src/screens/Settings/Settings.js | 1 + 6 files changed, 217 insertions(+), 8 deletions(-) create mode 100644 src/screens/ManagePrivileges.js diff --git a/src/App.js b/src/App.js index 79c7e8f9..e82ee775 100644 --- a/src/App.js +++ b/src/App.js @@ -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', diff --git a/src/lib/sfjs/privilegesManager.js b/src/lib/sfjs/privilegesManager.js index 117cdf71..4cc329c7 100644 --- a/src/lib/sfjs/privilegesManager.js +++ b/src/lib/sfjs/privilegesManager.js @@ -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: () => { diff --git a/src/screens/Authentication/Authenticate.js b/src/screens/Authentication/Authenticate.js index d5730180..ecb0b930 100644 --- a/src/screens/Authentication/Authenticate.js +++ b/src/screens/Authentication/Authenticate.js @@ -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 && + + + {this.sessionLengthOptions.map((option, index) => + {return option.value == this._sessionLength}} + onPress={() => {this.setSessionLength(option.value)}} + /> + )} + + } + ) @@ -222,12 +249,11 @@ export default class Authenticate extends Abstract { loadStyles() { this.styles = { - authSourceSection: { - }, authSourceSectionNotLast: { marginBottom: 10 }, - submitButtonCell: { + rememberForSection: { + marginTop: 10 } } } diff --git a/src/screens/ManagePrivileges.js b/src/screens/ManagePrivileges.js new file mode 100644 index 00000000..2d20e129 --- /dev/null +++ b/src/screens/ManagePrivileges.js @@ -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 (); + } + + return ( + + + + {this.state.sessionExpirey && !this.state.sessionExpired && + + + + + You will not be asked to authenticate until {this.state.sessionExpirey}. + + + + + } + + {this.state.availableActions.map((action, actionIndex) => + + + {this.state.availableCredentials.map((credential, credIndex) => + {return this.isCredentialRequiredForAction(action, credential)}} + onPress={() => {this.valueChanged(action, credential)}} + /> + )} + + )} + + + + + + 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. + + + Privileges sync across your other devices—however, 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. + + + + + + ); + } + + loadStyles() { + this.styles = { + section: { + marginBottom: 8 + }, + cellText: { + lineHeight: 19, + fontSize: 16, + color: StyleKit.variables.stylekitForegroundColor, + }, + aboutText: { + marginBottom: 8 + } + } + } + +} diff --git a/src/screens/Settings/Sections/OptionsSection.js b/src/screens/Settings/Sections/OptionsSection.js index d0b2a691..f204cde3 100644 --- a/src/screens/Settings/Sections/OptionsSection.js +++ b/src/screens/Settings/Sections/OptionsSection.js @@ -95,13 +95,15 @@ class OptionsSection extends Abstract { + + {signedIn && - + } {this.props.navigation.navigate("ManagePrivileges")}} />