mirror of
https://github.com/vector-im/element-ios.git
synced 2024-09-28 15:22:39 +00:00
Widgets: Check user permission before opening a widget (Data side)
#2832
This commit is contained in:
parent
da4edefdaf
commit
1eece82936
5 changed files with 252 additions and 1 deletions
|
@ -5,6 +5,8 @@ Improvements:
|
|||
* Integrations: Use the integrations manager provided by the homeserver admin via .well-known (#2815).
|
||||
* i18n: Add Welsh (cy).
|
||||
* SerializationService: Add deserialisation of Any.
|
||||
* RiotSharedSettings: New class to handle user settings shared accross Riot apps.
|
||||
* Widgets: Check user permission before opening a widget (TODO design: #2833).
|
||||
|
||||
Changes in 0.10.2 (2019-11-15)
|
||||
===============================================
|
||||
|
|
|
@ -70,6 +70,8 @@
|
|||
3275FD8C21A5A2C500B9C13D /* TermsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3275FD8B21A5A2C500B9C13D /* TermsView.swift */; };
|
||||
3281BCF72201FA4200F4A383 /* UIControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3281BCF62201FA4200F4A383 /* UIControl.swift */; };
|
||||
3284A35120A07C210044F922 /* postMessageAPI.js in Resources */ = {isa = PBXBuildFile; fileRef = 3284A35020A07C210044F922 /* postMessageAPI.js */; };
|
||||
32863A5A2384070300D07C4A /* RiotSharedSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32863A592384070300D07C4A /* RiotSharedSettings.swift */; };
|
||||
32863A5C2384074C00D07C4A /* RiotSettingAllowedWidgets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32863A5B2384074C00D07C4A /* RiotSettingAllowedWidgets.swift */; };
|
||||
32891D6B2264CBA300C82226 /* SimpleScreenTemplateViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32891D692264CBA300C82226 /* SimpleScreenTemplateViewController.swift */; };
|
||||
32891D6C2264CBA300C82226 /* SimpleScreenTemplateViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 32891D6A2264CBA300C82226 /* SimpleScreenTemplateViewController.storyboard */; };
|
||||
32891D702264DF7B00C82226 /* DeviceVerificationVerifiedViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 32891D6E2264DF7B00C82226 /* DeviceVerificationVerifiedViewController.storyboard */; };
|
||||
|
@ -706,6 +708,8 @@
|
|||
3275FD8B21A5A2C500B9C13D /* TermsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TermsView.swift; sourceTree = "<group>"; };
|
||||
3281BCF62201FA4200F4A383 /* UIControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIControl.swift; sourceTree = "<group>"; };
|
||||
3284A35020A07C210044F922 /* postMessageAPI.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = postMessageAPI.js; sourceTree = "<group>"; };
|
||||
32863A592384070300D07C4A /* RiotSharedSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RiotSharedSettings.swift; sourceTree = "<group>"; };
|
||||
32863A5B2384074C00D07C4A /* RiotSettingAllowedWidgets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RiotSettingAllowedWidgets.swift; sourceTree = "<group>"; };
|
||||
32891D692264CBA300C82226 /* SimpleScreenTemplateViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SimpleScreenTemplateViewController.swift; sourceTree = "<group>"; };
|
||||
32891D6A2264CBA300C82226 /* SimpleScreenTemplateViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = SimpleScreenTemplateViewController.storyboard; sourceTree = "<group>"; };
|
||||
32891D6E2264DF7B00C82226 /* DeviceVerificationVerifiedViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = DeviceVerificationVerifiedViewController.storyboard; sourceTree = "<group>"; };
|
||||
|
@ -1685,6 +1689,23 @@
|
|||
path = Incoming;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
32863A572384070300D07C4A /* Shared */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
32863A582384070300D07C4A /* JSONModels */,
|
||||
32863A592384070300D07C4A /* RiotSharedSettings.swift */,
|
||||
);
|
||||
path = Shared;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
32863A582384070300D07C4A /* JSONModels */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
32863A5B2384074C00D07C4A /* RiotSettingAllowedWidgets.swift */,
|
||||
);
|
||||
path = JSONModels;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
32891D682264C6A000C82226 /* SimpleScreenTemplate */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -3417,6 +3438,7 @@
|
|||
B1B5598A20EFC42100210D55 /* Settings */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
32863A572384070300D07C4A /* Shared */,
|
||||
B1B5597F20EFC3DF00210D55 /* RiotSettings.swift */,
|
||||
);
|
||||
path = Settings;
|
||||
|
@ -4413,6 +4435,7 @@
|
|||
B1CE9EFD22148703000FAE6A /* SignOutAlertPresenter.swift in Sources */,
|
||||
32F6B9692270623100BBA352 /* DeviceVerificationDataLoadingCoordinator.swift in Sources */,
|
||||
B125FE1D231D5DE400B72806 /* SettingsDiscoveryViewModel.swift in Sources */,
|
||||
32863A5A2384070300D07C4A /* RiotSharedSettings.swift in Sources */,
|
||||
B1B5594720EF7BD000210D55 /* RoomCollectionViewCell.m in Sources */,
|
||||
B10CFBC32268D99D00A5842E /* JitsiService.swift in Sources */,
|
||||
B1B558C120EF768F00210D55 /* RoomIncomingEncryptedAttachmentWithPaginationTitleBubbleCell.m in Sources */,
|
||||
|
@ -4514,6 +4537,7 @@
|
|||
B14F143522144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyViewController.swift in Sources */,
|
||||
B1DCC61E22E5E17100625807 /* EmojiPickerViewModel.swift in Sources */,
|
||||
B1B5574F20EE6C4D00210D55 /* RoomsViewController.m in Sources */,
|
||||
32863A5C2384074C00D07C4A /* RiotSettingAllowedWidgets.swift in Sources */,
|
||||
B1B9DEDA22E9B7350065E677 /* SerializationService.swift in Sources */,
|
||||
B1B5572520EE6C4D00210D55 /* RoomMessagesSearchViewController.m in Sources */,
|
||||
B139C22121FE5D9D00BB68EC /* KeyBackupRecoverFromPassphraseViewState.swift in Sources */,
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
Copyright 2019 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
|
||||
/// Model for "im.vector.setting.allowed_widgets"
|
||||
/// https://github.com/vector-im/riot-meta/blob/master/spec/settings.md#tracking-which-widgets-the-user-has-allowed-to-load
|
||||
struct RiotSettingAllowedWidgets {
|
||||
let widgets: [String: Bool]
|
||||
}
|
||||
|
||||
extension RiotSettingAllowedWidgets: Decodable {
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case widgets
|
||||
}
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
let widgets = try container.decode([String: Bool].self, forKey: .widgets)
|
||||
|
||||
self.init(widgets: widgets)
|
||||
}
|
||||
}
|
110
Riot/Managers/Settings/Shared/RiotSharedSettings.swift
Normal file
110
Riot/Managers/Settings/Shared/RiotSharedSettings.swift
Normal file
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
Copyright 2019 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
|
||||
import MatrixSDK
|
||||
|
||||
@objc enum WidgetPermission: Int {
|
||||
case undefined
|
||||
case granted
|
||||
case declined
|
||||
}
|
||||
|
||||
/// Shared user settings across all Riot clients.
|
||||
/// It implements https://github.com/vector-im/riot-meta/blob/master/spec/settings.md
|
||||
@objcMembers
|
||||
class RiotSharedSettings: NSObject {
|
||||
|
||||
// MARK: - Constants
|
||||
private enum Settings {
|
||||
static let breadcrumbs = "im.vector.setting.breadcrumbs"
|
||||
static let integrationProvisioning = "im.vector.setting.integration_provisioning"
|
||||
static let allowedWidgets = "im.vector.setting.allowed_widgets"
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Properties
|
||||
// MARK: Private
|
||||
private let session: MXSession
|
||||
private lazy var serializationService: SerializationServiceType = SerializationService()
|
||||
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
init(session: MXSession) {
|
||||
self.session = session
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
// MARK: Allowed widgets
|
||||
func permissionFor(widget: Widget) -> WidgetPermission {
|
||||
guard let allowedWidgets = getAllowedWidgets() else {
|
||||
return .undefined
|
||||
}
|
||||
|
||||
return allowedWidgets.widgets[widget.widgetEvent.eventId] == true ? .granted : .declined
|
||||
}
|
||||
|
||||
func getAllowedWidgets() -> RiotSettingAllowedWidgets? {
|
||||
guard let allowedWidgetsDict = getAccountData(forEventType: Settings.allowedWidgets) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
do {
|
||||
let allowedWidgets: RiotSettingAllowedWidgets = try serializationService.deserialize(allowedWidgetsDict)
|
||||
return allowedWidgets
|
||||
} catch {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@discardableResult func setPermissionFor(widget: Widget,
|
||||
permission: WidgetPermission,
|
||||
success: @escaping () -> Void,
|
||||
failure: @escaping (Error?) -> Void)
|
||||
-> MXHTTPOperation? {
|
||||
|
||||
guard let widgetEventId = widget.widgetEvent.eventId else {
|
||||
return nil
|
||||
}
|
||||
|
||||
var widgets = getAllowedWidgets()?.widgets ?? [:]
|
||||
|
||||
switch permission {
|
||||
case .undefined:
|
||||
widgets.removeValue(forKey: widgetEventId)
|
||||
case .granted:
|
||||
widgets[widgetEventId] = true
|
||||
case .declined:
|
||||
widgets[widgetEventId] = false
|
||||
}
|
||||
|
||||
// Update only the "widgets" field in the account data
|
||||
var allowedWidgetsDict = getAccountData(forEventType: Settings.allowedWidgets) ?? [:]
|
||||
allowedWidgetsDict[RiotSettingAllowedWidgets.CodingKeys.widgets.rawValue] = widgets
|
||||
|
||||
return session.setAccountData(allowedWidgetsDict, forType: Settings.allowedWidgets, success: success, failure: failure)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Private
|
||||
private func getAccountData(forEventType eventType: String) -> [String: Any]? {
|
||||
return session.accountData.accountData(forEventType: eventType) as? [String: Any]
|
||||
}
|
||||
}
|
|
@ -26,6 +26,7 @@ NSString *const kJavascriptSendResponseToPostMessageAPI = @"riotIOS.sendResponse
|
|||
@interface WidgetViewController () <ServiceTermsModalCoordinatorBridgePresenterDelegate>
|
||||
|
||||
@property (nonatomic, strong) ServiceTermsModalCoordinatorBridgePresenter *serviceTermsModalCoordinatorBridgePresenter;
|
||||
@property (nonatomic, strong) NSString *widgetUrl;
|
||||
|
||||
@end
|
||||
|
||||
|
@ -34,9 +35,10 @@ NSString *const kJavascriptSendResponseToPostMessageAPI = @"riotIOS.sendResponse
|
|||
|
||||
- (instancetype)initWithUrl:(NSString*)widgetUrl forWidget:(Widget*)theWidget
|
||||
{
|
||||
self = [super initWithURL:widgetUrl];
|
||||
self = [super initWithURL:nil];
|
||||
if (self)
|
||||
{
|
||||
self.widgetUrl = widgetUrl;
|
||||
widget = theWidget;
|
||||
}
|
||||
return self;
|
||||
|
@ -57,6 +59,23 @@ NSString *const kJavascriptSendResponseToPostMessageAPI = @"riotIOS.sendResponse
|
|||
}
|
||||
}
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated
|
||||
{
|
||||
[super viewDidAppear:animated];
|
||||
|
||||
// Check widget permission before opening the widget
|
||||
[self checkWidgetPermissionWithCompletion:^(BOOL granted) {
|
||||
if (granted)
|
||||
{
|
||||
self.URL = self.widgetUrl;
|
||||
}
|
||||
else
|
||||
{
|
||||
[self withdrawViewControllerAnimated:YES completion:nil];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)showErrorAsAlert:(NSError*)error
|
||||
{
|
||||
NSString *title = [error.userInfo valueForKey:NSLocalizedFailureReasonErrorKey];
|
||||
|
@ -94,6 +113,66 @@ NSString *const kJavascriptSendResponseToPostMessageAPI = @"riotIOS.sendResponse
|
|||
[self presentViewController:alert animated:YES completion:nil];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Widget Permission
|
||||
|
||||
- (void)checkWidgetPermissionWithCompletion:(void (^)(BOOL granted))completion
|
||||
{
|
||||
// Check permission in user settings
|
||||
MXSession *session = widget.mxSession;
|
||||
|
||||
__block RiotSharedSettings *sharedSettings = [[RiotSharedSettings alloc] initWithSession:session];
|
||||
|
||||
WidgetPermission permission = [sharedSettings permissionForWidget:widget];
|
||||
if (permission == WidgetPermissionGranted)
|
||||
{
|
||||
completion(YES);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Note: ask permission again if the user previously declined it
|
||||
[self askPermissionWithCompletion:^(BOOL granted) {
|
||||
// Update the settings in user account data in parallel
|
||||
[sharedSettings setPermissionForWidget:self.widget
|
||||
permission:granted ? WidgetPermissionGranted : WidgetPermissionDeclined
|
||||
success:^
|
||||
{
|
||||
sharedSettings = nil;
|
||||
}
|
||||
failure:^(NSError * _Nullable error)
|
||||
{
|
||||
NSLog(@"[WidgetVC] setPermissionForWidget failed. Error: %@", error);
|
||||
sharedSettings = nil;
|
||||
}];
|
||||
|
||||
completion(granted);
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)askPermissionWithCompletion:(void (^)(BOOL granted))completion
|
||||
{
|
||||
// TODO: Implement the design
|
||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Load Widget"
|
||||
message:@"blabla"
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
|
||||
[alert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"continue"]
|
||||
style:UIAlertActionStyleDefault
|
||||
handler:^(UIAlertAction * action) {
|
||||
completion(YES);
|
||||
}]];
|
||||
|
||||
[alert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"decline", @"Vector", nil)
|
||||
style:UIAlertActionStyleCancel
|
||||
handler:^(UIAlertAction * action) {
|
||||
completion(NO);
|
||||
}]];
|
||||
|
||||
[self presentViewController:alert animated:YES completion:nil];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - WKNavigationDelegate
|
||||
|
||||
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
|
||||
|
|
Loading…
Reference in a new issue