mirror of
https://github.com/vector-im/element-ios.git
synced 2024-09-28 23:32:41 +00:00
Merge pull request #6250 from vector-im/ismail/6180_support_prov_links
This commit is contained in:
commit
ceae4a455d
19 changed files with 398 additions and 343 deletions
|
@ -112,7 +112,7 @@ UINavigationControllerDelegate
|
|||
/**
|
||||
Last handled universal link (url will be formatted for several hash keys).
|
||||
*/
|
||||
@property (nonatomic, readonly) UniversalLink *lastHandledUniversalLink;
|
||||
@property (nonatomic, copy, readonly) UniversalLink *lastHandledUniversalLink;
|
||||
|
||||
// New message sound id.
|
||||
@property (nonatomic, readonly) SystemSoundID messageSound;
|
||||
|
@ -162,6 +162,9 @@ UINavigationControllerDelegate
|
|||
// Reload all running matrix sessions
|
||||
- (void)reloadMatrixSessions:(BOOL)clearCache;
|
||||
|
||||
- (void)displayLogoutConfirmationForLink:(UniversalLink *)link
|
||||
completion:(void (^)(BOOL loggedOut))completion;
|
||||
|
||||
/**
|
||||
Log out all the accounts after asking for a potential confirmation.
|
||||
Show the authentication screen on successful logout.
|
||||
|
@ -252,19 +255,6 @@ UINavigationControllerDelegate
|
|||
*/
|
||||
- (BOOL)handleUniversalLinkWithParameters:(UniversalLinkParameters*)parameters;
|
||||
|
||||
/**
|
||||
Extract params from the URL fragment part (after '#') of a vector.im Universal link:
|
||||
|
||||
The fragment can contain a '?'. So there are two kinds of parameters: path params and query params.
|
||||
It is in the form of /[pathParam1]/[pathParam2]?[queryParam1Key]=[queryParam1Value]&[queryParam2Key]=[queryParam2Value]
|
||||
@note this method should be private but is used by RoomViewController. This should be moved to a univresal link parser class
|
||||
|
||||
@param fragment the fragment to parse.
|
||||
@param outPathParams the decoded path params.
|
||||
@param outQueryParams the decoded query params. If there is no query params, it will be nil.
|
||||
*/
|
||||
- (void)parseUniversalLinkFragment:(NSString*)fragment outPathParams:(NSArray<NSString*> **)outPathParams outQueryParams:(NSMutableDictionary **)outQueryParams;
|
||||
|
||||
/**
|
||||
Open the dedicated space with the given ID.
|
||||
|
||||
|
|
|
@ -1170,19 +1170,17 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
|
|||
webURL = [Tools fixURLWithSeveralHashKeys:webURL];
|
||||
|
||||
// Extract required parameters from the link
|
||||
NSArray<NSString*> *pathParams;
|
||||
NSMutableDictionary *queryParams;
|
||||
[self parseUniversalLinkFragment:webURL.absoluteString outPathParams:&pathParams outQueryParams:&queryParams];
|
||||
UniversalLink *newLink = [[UniversalLink alloc] initWithUrl:webURL];
|
||||
NSDictionary<NSString*, NSString*> *queryParams = newLink.queryParams;
|
||||
|
||||
UniversalLink *newLink = [[UniversalLink alloc] initWithUrl:webURL pathParams:pathParams queryParams:queryParams];
|
||||
if (![_lastHandledUniversalLink isEqual:newLink])
|
||||
{
|
||||
_lastHandledUniversalLink = [[UniversalLink alloc] initWithUrl:webURL pathParams:pathParams queryParams:queryParams];
|
||||
_lastHandledUniversalLink = newLink;
|
||||
// notify this change
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:AppDelegateUniversalLinkDidChangeNotification object:nil];
|
||||
}
|
||||
|
||||
if ([self handleServerProvisioningLink:webURL])
|
||||
if ([AuthenticationService.shared handleServerProvisioningLink:newLink])
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
@ -1281,20 +1279,19 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
|
|||
return YES;
|
||||
}
|
||||
|
||||
return [self handleUniversalLinkFragment:webURL.fragment fromURL:webURL];
|
||||
return [self handleUniversalLinkFragment:webURL.fragment fromLink:newLink];
|
||||
}
|
||||
|
||||
- (BOOL)handleUniversalLinkFragment:(NSString*)fragment fromURL:(NSURL*)universalLinkURL
|
||||
|
||||
- (BOOL)handleUniversalLinkFragment:(NSString*)fragment fromLink:(UniversalLink*)universalLink
|
||||
{
|
||||
if (!fragment || !universalLinkURL)
|
||||
if (!fragment || !universalLink)
|
||||
{
|
||||
MXLogDebug(@"[AppDelegate] Cannot handle universal link with missing data: %@ %@", fragment, universalLinkURL);
|
||||
MXLogDebug(@"[AppDelegate] Cannot handle universal link with missing data: %@ %@", fragment, universalLink.url);
|
||||
return NO;
|
||||
}
|
||||
ScreenPresentationParameters *presentationParameters = [[ScreenPresentationParameters alloc] initWithRestoreInitialDisplay:YES stackAboveVisibleViews:NO];
|
||||
|
||||
UniversalLinkParameters *parameters = [[UniversalLinkParameters alloc] initWithFragment:fragment universalLinkURL:universalLinkURL presentationParameters:presentationParameters];
|
||||
UniversalLinkParameters *parameters = [[UniversalLinkParameters alloc] initWithFragment:fragment universalLink:universalLink presentationParameters:presentationParameters];
|
||||
|
||||
return [self handleUniversalLinkWithParameters:parameters];
|
||||
}
|
||||
|
@ -1302,7 +1299,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
|
|||
- (BOOL)handleUniversalLinkWithParameters:(UniversalLinkParameters*)universalLinkParameters
|
||||
{
|
||||
NSString *fragment = universalLinkParameters.fragment;
|
||||
NSURL *universalLinkURL = universalLinkParameters.universalLinkURL;
|
||||
UniversalLink *universalLink = universalLinkParameters.universalLink;
|
||||
ScreenPresentationParameters *presentationParameters = universalLinkParameters.presentationParameters;
|
||||
BOOL restoreInitialDisplay = presentationParameters.restoreInitialDisplay;
|
||||
|
||||
|
@ -1320,9 +1317,8 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
|
|||
[self resetPendingUniversalLink];
|
||||
|
||||
// Extract params
|
||||
NSArray<NSString*> *pathParams;
|
||||
NSMutableDictionary *queryParams;
|
||||
[self parseUniversalLinkFragment:fragment outPathParams:&pathParams outQueryParams:&queryParams];
|
||||
NSArray<NSString*> *pathParams = universalLink.pathParams;
|
||||
NSDictionary<NSString*, NSString*> *queryParams = universalLink.queryParams;
|
||||
|
||||
// Sanity check
|
||||
if (!pathParams.count)
|
||||
|
@ -1506,7 +1502,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
|
|||
self->universalLinkFragmentPendingRoomAlias = @{resolution.roomId: roomIdOrAlias};
|
||||
|
||||
UniversalLinkParameters *newParameters = [[UniversalLinkParameters alloc] initWithFragment:newFragment
|
||||
universalLinkURL:universalLinkURL
|
||||
universalLink:universalLink
|
||||
presentationParameters:presentationParameters];
|
||||
[self handleUniversalLinkWithParameters:newParameters];
|
||||
}
|
||||
|
@ -1692,14 +1688,6 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
|
|||
}];
|
||||
}
|
||||
}
|
||||
// Check whether this is a registration links.
|
||||
else if ([pathParams[0] isEqualToString:@"register"])
|
||||
{
|
||||
MXLogDebug(@"[AppDelegate] Universal link with registration parameters");
|
||||
continueUserActivity = YES;
|
||||
|
||||
[_masterTabBarController showOnboardingFlowWithRegistrationParameters:queryParams];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Unknown command: Do nothing except coming back to the main screen
|
||||
|
@ -1764,167 +1752,44 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
|
|||
}];
|
||||
}
|
||||
|
||||
/**
|
||||
Extract params from the URL fragment part (after '#') of a vector.im Universal link:
|
||||
|
||||
The fragment can contain a '?'. So there are two kinds of parameters: path params and query params.
|
||||
It is in the form of /[pathParam1]/[pathParam2]?[queryParam1Key]=[queryParam1Value]&[queryParam2Key]=[queryParam2Value]
|
||||
|
||||
@param fragment the fragment to parse.
|
||||
@param outPathParams the decoded path params.
|
||||
@param outQueryParams the decoded query params. If there is no query params, it will be nil.
|
||||
*/
|
||||
- (void)parseUniversalLinkFragment:(NSString*)fragment outPathParams:(NSArray<NSString*> **)outPathParams outQueryParams:(NSMutableDictionary **)outQueryParams
|
||||
{
|
||||
NSParameterAssert(outPathParams && outQueryParams);
|
||||
|
||||
NSArray<NSString*> *pathParams;
|
||||
NSMutableDictionary *queryParams;
|
||||
|
||||
NSArray<NSString*> *fragments = [fragment componentsSeparatedByString:@"?"];
|
||||
|
||||
// Extract path params
|
||||
pathParams = [fragments[0] componentsSeparatedByString:@"/"];
|
||||
|
||||
// Remove the first empty path param string
|
||||
pathParams = [pathParams filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"length > 0"]];
|
||||
|
||||
// URL decode each path param
|
||||
NSMutableArray<NSString*> *pathParams2 = [NSMutableArray arrayWithArray:pathParams];
|
||||
for (NSInteger i = 0; i < pathParams.count; i++)
|
||||
{
|
||||
pathParams2[i] = [pathParams2[i] stringByRemovingPercentEncoding];
|
||||
}
|
||||
pathParams = pathParams2;
|
||||
|
||||
// Extract query params if any
|
||||
// Query params are in the form [queryParam1Key]=[queryParam1Value], so the
|
||||
// presence of at least one '=' character is mandatory
|
||||
if (fragments.count == 2 && (NSNotFound != [fragments[1] rangeOfString:@"="].location))
|
||||
{
|
||||
queryParams = [[NSMutableDictionary alloc] init];
|
||||
for (NSString *keyValue in [fragments[1] componentsSeparatedByString:@"&"])
|
||||
{
|
||||
// Get the parameter name
|
||||
NSString *key = [keyValue componentsSeparatedByString:@"="][0];
|
||||
|
||||
// Get the parameter value
|
||||
NSString *value = [keyValue componentsSeparatedByString:@"="][1];
|
||||
if (value.length)
|
||||
{
|
||||
value = [value stringByReplacingOccurrencesOfString:@"+" withString:@" "];
|
||||
value = [value stringByRemovingPercentEncoding];
|
||||
|
||||
if ([key isEqualToString:@"via"])
|
||||
{
|
||||
// Special case the via parameter
|
||||
// As we can have several of them, store each value into an array
|
||||
if (!queryParams[key])
|
||||
{
|
||||
queryParams[key] = [NSMutableArray array];
|
||||
}
|
||||
|
||||
[queryParams[key] addObject:value];
|
||||
}
|
||||
else
|
||||
{
|
||||
queryParams[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*outPathParams = pathParams;
|
||||
*outQueryParams = queryParams;
|
||||
}
|
||||
|
||||
/**
|
||||
Parse and handle a server provisioning link. Returns `YES` if a provisioning link was detected and handled.
|
||||
@param link A link such as https://mobile.element.io/?hs_url=matrix.example.com&is_url=identity.example.com
|
||||
*/
|
||||
- (BOOL)handleServerProvisioningLink:(NSURL*)link
|
||||
{
|
||||
MXLogDebug(@"[AppDelegate] handleServerProvisioningLink: %@", link);
|
||||
|
||||
NSString *homeserver, *identityServer;
|
||||
[self parseServerProvisioningLink:link homeserver:&homeserver identityServer:&identityServer];
|
||||
|
||||
if (homeserver)
|
||||
{
|
||||
if ([MXKAccountManager sharedManager].activeAccounts.count)
|
||||
{
|
||||
[self displayServerProvisioningLinkBuyAlreadyLoggedInAlertWithCompletion:^(BOOL logout) {
|
||||
|
||||
MXLogDebug(@"[AppDelegate] handleServerProvisioningLink: logoutWithConfirmation: logout: %@", @(logout));
|
||||
if (logout)
|
||||
{
|
||||
[self logoutWithConfirmation:NO completion:^(BOOL isLoggedOut) {
|
||||
[self handleServerProvisioningLink:link];
|
||||
}];
|
||||
}
|
||||
}];
|
||||
}
|
||||
else
|
||||
{
|
||||
[_masterTabBarController showOnboardingFlow];
|
||||
[_masterTabBarController.onboardingCoordinatorBridgePresenter updateHomeserver:homeserver andIdentityServer:identityServer];
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)parseServerProvisioningLink:(NSURL*)link homeserver:(NSString**)homeserver identityServer:(NSString**)identityServer
|
||||
{
|
||||
if ([link.path isEqualToString:@"/"])
|
||||
{
|
||||
NSURLComponents *linkURLComponents = [NSURLComponents componentsWithURL:link resolvingAgainstBaseURL:NO];
|
||||
for (NSURLQueryItem *item in linkURLComponents.queryItems)
|
||||
{
|
||||
if ([item.name isEqualToString:@"hs_url"])
|
||||
{
|
||||
*homeserver = item.value;
|
||||
}
|
||||
else if ([item.name isEqualToString:@"is_url"])
|
||||
{
|
||||
*identityServer = item.value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
MXLogDebug(@"[AppDelegate] parseServerProvisioningLink: Error: Unknown path: %@", link.path);
|
||||
}
|
||||
|
||||
|
||||
MXLogDebug(@"[AppDelegate] parseServerProvisioningLink: homeserver: %@ - identityServer: %@", *homeserver, *identityServer);
|
||||
}
|
||||
|
||||
- (void)displayServerProvisioningLinkBuyAlreadyLoggedInAlertWithCompletion:(void (^)(BOOL logout))completion
|
||||
- (void)displayLogoutConfirmationForLink:(UniversalLink *)link
|
||||
completion:(void (^)(BOOL loggedOut))completion
|
||||
{
|
||||
// Ask confirmation
|
||||
self.logoutConfirmation = [UIAlertController alertControllerWithTitle:[VectorL10n errorUserAlreadyLoggedIn] message:nil preferredStyle:UIAlertControllerStyleAlert];
|
||||
self.logoutConfirmation = [UIAlertController alertControllerWithTitle:[VectorL10n errorUserAlreadyLoggedIn]
|
||||
message:nil
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
|
||||
[self.logoutConfirmation addAction:[UIAlertAction actionWithTitle:[VectorL10n settingsSignOut]
|
||||
style:UIAlertActionStyleDefault
|
||||
handler:^(UIAlertAction * action)
|
||||
{
|
||||
self.logoutConfirmation = nil;
|
||||
completion(YES);
|
||||
}]];
|
||||
self.logoutConfirmation = nil;
|
||||
[self logoutWithConfirmation:NO completion:^(BOOL isLoggedOut) {
|
||||
if (isLoggedOut)
|
||||
{
|
||||
// process the link again after logging out
|
||||
[AuthenticationService.shared handleServerProvisioningLink:link];
|
||||
}
|
||||
if (completion)
|
||||
{
|
||||
completion(YES);
|
||||
}
|
||||
}];
|
||||
}]];
|
||||
|
||||
[self.logoutConfirmation addAction:[UIAlertAction actionWithTitle:[VectorL10n cancel]
|
||||
style:UIAlertActionStyleCancel
|
||||
handler:^(UIAlertAction * action)
|
||||
{
|
||||
self.logoutConfirmation = nil;
|
||||
completion(NO);
|
||||
}]];
|
||||
self.logoutConfirmation = nil;
|
||||
if (completion)
|
||||
{
|
||||
completion(NO);
|
||||
}
|
||||
}]];
|
||||
|
||||
[self.logoutConfirmation mxk_setAccessibilityIdentifier: @"AppDelegateLogoutConfirmationAlert"];
|
||||
[self.logoutConfirmation mxk_setAccessibilityIdentifier:@"AppDelegateLogoutConfirmationAlert"];
|
||||
[self showNotificationAlert:self.logoutConfirmation];
|
||||
}
|
||||
|
||||
|
|
|
@ -39,17 +39,10 @@ protocol AuthenticationCoordinatorProtocol: Coordinator, Presentable {
|
|||
|
||||
/// Update the screen to display registration or login.
|
||||
func update(authenticationFlow: AuthenticationFlow)
|
||||
|
||||
/// Force a registration process based on a predefined set of parameters from a server provisioning link.
|
||||
/// For more information see `AuthenticationViewController.externalRegistrationParameters`.
|
||||
func update(externalRegistrationParameters: [AnyHashable: Any])
|
||||
|
||||
|
||||
/// Update the screen to use any credentials to use after a soft logout has taken place.
|
||||
func update(softLogoutCredentials: MXCredentials)
|
||||
|
||||
/// Set up the authentication screen with the specified homeserver and/or identity server.
|
||||
func updateHomeserver(_ homeserver: String?, andIdentityServer identityServer: String?)
|
||||
|
||||
|
||||
/// Indicates to the coordinator to display any pending screens if it was created with
|
||||
/// the `canPresentAdditionalScreens` parameter set to `false`
|
||||
func presentPendingScreensIfNecessary()
|
||||
|
|
|
@ -87,18 +87,10 @@ final class LegacyAuthenticationCoordinator: NSObject, AuthenticationCoordinator
|
|||
authenticationViewController.authType = authenticationFlow.mxkType
|
||||
}
|
||||
|
||||
func update(externalRegistrationParameters: [AnyHashable: Any]) {
|
||||
authenticationViewController.externalRegistrationParameters = externalRegistrationParameters
|
||||
}
|
||||
|
||||
func update(softLogoutCredentials: MXCredentials) {
|
||||
authenticationViewController.softLogoutCredentials = softLogoutCredentials
|
||||
}
|
||||
|
||||
func updateHomeserver(_ homeserver: String?, andIdentityServer identityServer: String?) {
|
||||
authenticationViewController.showCustomHomeserver(homeserver, andIdentityServer: identityServer)
|
||||
}
|
||||
|
||||
|
||||
func presentPendingScreensIfNecessary() {
|
||||
canPresentAdditionalScreens = true
|
||||
|
||||
|
@ -150,6 +142,15 @@ extension LegacyAuthenticationCoordinator: AuthenticationServiceDelegate {
|
|||
func authenticationService(_ service: AuthenticationService, didReceive ssoLoginToken: String, with transactionID: String) -> Bool {
|
||||
authenticationViewController.continueSSOLogin(withToken: ssoLoginToken, txnId: transactionID)
|
||||
}
|
||||
|
||||
func authenticationService(_ service: AuthenticationService, didUpdateStateWithLink link: UniversalLink) {
|
||||
if link.pathParams.first == "register" && !link.queryParams.isEmpty {
|
||||
authenticationViewController.externalRegistrationParameters = link.queryParams
|
||||
} else if let homeserver = link.homeserverUrl {
|
||||
let identityServer = link.identityServerUrl
|
||||
authenticationViewController.showCustomHomeserver(homeserver, andIdentityServer: identityServer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - AuthenticationViewControllerDelegate
|
||||
|
|
|
@ -22,8 +22,8 @@ class UniversalLinkParameters: NSObject {
|
|||
|
||||
// MARK: - Properties
|
||||
|
||||
/// The unprocessed universal link URL
|
||||
let universalLinkURL: URL
|
||||
/// The universal link
|
||||
let universalLink: UniversalLink
|
||||
|
||||
/// The fragment part of the universal link
|
||||
let fragment: String
|
||||
|
@ -34,22 +34,33 @@ class UniversalLinkParameters: NSObject {
|
|||
// MARK: - Setup
|
||||
|
||||
init(fragment: String,
|
||||
universalLinkURL: URL,
|
||||
universalLink: UniversalLink,
|
||||
presentationParameters: ScreenPresentationParameters) {
|
||||
self.fragment = fragment
|
||||
self.universalLinkURL = universalLinkURL
|
||||
self.universalLink = universalLink
|
||||
self.presentationParameters = presentationParameters
|
||||
|
||||
super.init()
|
||||
}
|
||||
|
||||
convenience init?(universalLinkURL: URL,
|
||||
convenience init?(universalLink: UniversalLink,
|
||||
presentationParameters: ScreenPresentationParameters) {
|
||||
|
||||
guard let fixedURL = Tools.fixURL(withSeveralHashKeys: universalLinkURL), let fragment = fixedURL.fragment else {
|
||||
guard let fixedURL = Tools.fixURL(withSeveralHashKeys: universalLink.url), let fragment = fixedURL.fragment else {
|
||||
return nil
|
||||
}
|
||||
|
||||
self.init(fragment: fragment, universalLinkURL: universalLinkURL, presentationParameters: presentationParameters)
|
||||
self.init(fragment: fragment, universalLink: universalLink, presentationParameters: presentationParameters)
|
||||
}
|
||||
|
||||
convenience init?(url: URL,
|
||||
presentationParameters: ScreenPresentationParameters) {
|
||||
|
||||
guard let fixedURL = Tools.fixURL(withSeveralHashKeys: url), let fragment = fixedURL.fragment else {
|
||||
return nil
|
||||
}
|
||||
let universalLink = UniversalLink(url: fixedURL)
|
||||
|
||||
self.init(fragment: fragment, universalLink: universalLink, presentationParameters: presentationParameters)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
*/
|
||||
|
||||
import UIKit
|
||||
import CommonKit
|
||||
|
||||
struct AuthenticationCoordinatorParameters {
|
||||
let navigationRouter: NavigationRouterType
|
||||
|
@ -59,6 +60,9 @@ final class AuthenticationCoordinator: NSObject, AuthenticationCoordinatorProtoc
|
|||
|
||||
/// The listener object that informs the coordinator whether verification needs to be presented or not.
|
||||
private var verificationListener: SessionVerificationListener?
|
||||
|
||||
private var indicatorPresenter: UserIndicatorTypePresenterProtocol
|
||||
private var successIndicator: UserIndicator?
|
||||
|
||||
/// The password entered, for use when setting up cross-signing.
|
||||
private var password: String?
|
||||
|
@ -77,6 +81,8 @@ final class AuthenticationCoordinator: NSObject, AuthenticationCoordinatorProtoc
|
|||
self.navigationRouter = parameters.navigationRouter
|
||||
self.initialScreen = parameters.initialScreen
|
||||
self.canPresentAdditionalScreens = parameters.canPresentAdditionalScreens
|
||||
|
||||
indicatorPresenter = UserIndicatorTypePresenter(presentingViewController: parameters.navigationRouter.toPresentable())
|
||||
|
||||
super.init()
|
||||
}
|
||||
|
@ -613,6 +619,15 @@ extension AuthenticationCoordinator: AuthenticationServiceDelegate {
|
|||
|
||||
return true
|
||||
}
|
||||
|
||||
func authenticationService(_ service: AuthenticationService, didUpdateStateWithLink link: UniversalLink) {
|
||||
if link.pathParams.first == "register" {
|
||||
callback?(.cancel(.register))
|
||||
} else {
|
||||
callback?(.cancel(.login))
|
||||
}
|
||||
successIndicator = indicatorPresenter.present(.success(label: VectorL10n.done))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - KeyVerificationCoordinatorDelegate
|
||||
|
@ -657,17 +672,9 @@ extension AuthenticationCoordinator {
|
|||
// unused
|
||||
}
|
||||
|
||||
func update(externalRegistrationParameters: [AnyHashable: Any]) {
|
||||
// unused
|
||||
}
|
||||
|
||||
func update(softLogoutCredentials: MXCredentials) {
|
||||
// unused
|
||||
}
|
||||
|
||||
func updateHomeserver(_ homeserver: String?, andIdentityServer identityServer: String?) {
|
||||
// unused
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - AuthFallBackViewControllerDelegate
|
||||
|
|
|
@ -42,13 +42,6 @@ final class OnboardingCoordinator: NSObject, OnboardingCoordinatorProtocol {
|
|||
// MARK: Private
|
||||
|
||||
private let parameters: OnboardingCoordinatorParameters
|
||||
// TODO: these can likely be consolidated using an additional authType.
|
||||
/// The any registration parameters for AuthenticationViewController from a server provisioning link.
|
||||
private var externalRegistrationParameters: [AnyHashable: Any]?
|
||||
/// A custom homeserver to be shown when logging in.
|
||||
private var customHomeserver: String?
|
||||
/// A custom identity server to be used once logged in.
|
||||
private var customIdentityServer: String?
|
||||
|
||||
// MARK: Navigation State
|
||||
private var navigationRouter: NavigationRouterType {
|
||||
|
@ -113,21 +106,7 @@ final class OnboardingCoordinator: NSObject, OnboardingCoordinatorProtocol {
|
|||
func toPresentable() -> UIViewController {
|
||||
navigationRouter.toPresentable()
|
||||
}
|
||||
|
||||
/// Force a registration process based on a predefined set of parameters from a server provisioning link.
|
||||
/// For more information see `AuthenticationViewController.externalRegistrationParameters`.
|
||||
func update(externalRegistrationParameters: [AnyHashable: Any]) {
|
||||
self.externalRegistrationParameters = externalRegistrationParameters
|
||||
legacyAuthenticationCoordinator.update(externalRegistrationParameters: externalRegistrationParameters)
|
||||
}
|
||||
|
||||
/// Set up the authentication screen with the specified homeserver and/or identity server.
|
||||
func updateHomeserver(_ homeserver: String?, andIdentityServer identityServer: String?) {
|
||||
self.customHomeserver = homeserver
|
||||
self.customIdentityServer = identityServer
|
||||
legacyAuthenticationCoordinator.updateHomeserver(homeserver, andIdentityServer: identityServer)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Pre-Authentication
|
||||
|
||||
/// Show the onboarding splash screen as the root module in the flow.
|
||||
|
@ -259,14 +238,7 @@ final class OnboardingCoordinator: NSObject, OnboardingCoordinatorProtocol {
|
|||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Due to needing to preload the authVC, this breaks the Coordinator init/start pattern.
|
||||
// This can be re-assessed once we re-write a native flow for authentication.
|
||||
|
||||
if let externalRegistrationParameters = externalRegistrationParameters {
|
||||
coordinator.update(externalRegistrationParameters: externalRegistrationParameters)
|
||||
}
|
||||
|
||||
|
||||
coordinator.customServerFieldsVisible = useCaseResult == .customServer
|
||||
|
||||
if let softLogoutCredentials = parameters.softLogoutCredentials {
|
||||
|
@ -277,11 +249,7 @@ final class OnboardingCoordinator: NSObject, OnboardingCoordinatorProtocol {
|
|||
|
||||
coordinator.start()
|
||||
add(childCoordinator: coordinator)
|
||||
|
||||
if customHomeserver != nil || customIdentityServer != nil {
|
||||
coordinator.updateHomeserver(customHomeserver, andIdentityServer: customIdentityServer)
|
||||
}
|
||||
|
||||
|
||||
if navigationRouter.modules.isEmpty {
|
||||
navigationRouter.setRootModule(coordinator, popCompletion: nil)
|
||||
} else {
|
||||
|
|
|
@ -20,8 +20,6 @@ import Foundation
|
|||
|
||||
@objcMembers
|
||||
class OnboardingCoordinatorBridgePresenterParameters: NSObject {
|
||||
/// The external registration parameters for AuthenticationViewController.
|
||||
var externalRegistrationParameters: [AnyHashable: Any]?
|
||||
/// The credentials to use after a soft logout has taken place.
|
||||
var softLogoutCredentials: MXCredentials?
|
||||
}
|
||||
|
@ -86,17 +84,6 @@ final class OnboardingCoordinatorBridgePresenter: NSObject {
|
|||
self.navigationType = .push
|
||||
}
|
||||
|
||||
/// Force a registration process based on a predefined set of parameters from a server provisioning link.
|
||||
/// For more information see `AuthenticationViewController.externalRegistrationParameters`.
|
||||
func update(externalRegistrationParameters: [AnyHashable: Any]) {
|
||||
coordinator?.update(externalRegistrationParameters: externalRegistrationParameters)
|
||||
}
|
||||
|
||||
/// Set up the authentication screen with the specified homeserver and/or identity server.
|
||||
func updateHomeserver(_ homeserver: String?, andIdentityServer identityServer: String?) {
|
||||
coordinator?.updateHomeserver(homeserver, andIdentityServer: identityServer)
|
||||
}
|
||||
|
||||
func dismiss(animated: Bool, completion: (() -> Void)?) {
|
||||
guard let coordinator = self.coordinator else {
|
||||
return
|
||||
|
@ -137,10 +124,6 @@ final class OnboardingCoordinatorBridgePresenter: NSObject {
|
|||
onboardingCoordinator.completion = { [weak self] in
|
||||
self?.completion?()
|
||||
}
|
||||
if let externalRegistrationParameters = parameters.externalRegistrationParameters {
|
||||
onboardingCoordinator.update(externalRegistrationParameters: externalRegistrationParameters)
|
||||
}
|
||||
|
||||
return onboardingCoordinator
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,11 +20,4 @@ import Foundation
|
|||
/// full onboarding flow with pre-auth screens, authentication and setup screens once signed in.
|
||||
protocol OnboardingCoordinatorProtocol: Coordinator, Presentable {
|
||||
var completion: (() -> Void)? { get set }
|
||||
|
||||
/// Force a registration process based on a predefined set of parameters from a server provisioning link.
|
||||
/// For more information see `AuthenticationViewController.externalRegistrationParameters`.
|
||||
func update(externalRegistrationParameters: [AnyHashable: Any])
|
||||
|
||||
/// Set up the authentication screen with the specified homeserver and/or identity server.
|
||||
func updateHomeserver(_ homeserver: String?, andIdentityServer identityServer: String?)
|
||||
}
|
||||
|
|
|
@ -2375,16 +2375,21 @@ static CGSize kThreadListBarButtonItemImageSize;
|
|||
return [[ScreenPresentationParameters alloc] initWithRestoreInitialDisplay:NO stackAboveVisibleViews:BuildSettings.allowSplitViewDetailsScreenStacking sender:self sourceView:nil];
|
||||
}
|
||||
|
||||
- (BOOL)handleUniversalLinkURL:(NSURL*)universalLinkURL
|
||||
- (BOOL)handleUniversalLinkURL:(NSURL*)url
|
||||
{
|
||||
UniversalLinkParameters *parameters = [[UniversalLinkParameters alloc] initWithUniversalLinkURL:universalLinkURL presentationParameters:[self buildUniversalLinkPresentationParameters]];
|
||||
ScreenPresentationParameters *screenParameters = [self buildUniversalLinkPresentationParameters];
|
||||
UniversalLinkParameters *parameters = [[UniversalLinkParameters alloc] initWithUrl:url
|
||||
presentationParameters:screenParameters];
|
||||
return [self handleUniversalLinkWithParameters:parameters];
|
||||
}
|
||||
|
||||
- (BOOL)handleUniversalLinkFragment:(NSString*)fragment fromURL:(NSURL*)universalLinkURL
|
||||
- (BOOL)handleUniversalLinkFragment:(NSString*)fragment fromURL:(NSURL*)url
|
||||
{
|
||||
ScreenPresentationParameters *screenParameters = [self buildUniversalLinkPresentationParameters];
|
||||
UniversalLink *universalLink = [[UniversalLink alloc] initWithUrl:url];
|
||||
UniversalLinkParameters *parameters = [[UniversalLinkParameters alloc] initWithFragment:fragment
|
||||
universalLinkURL:universalLinkURL presentationParameters:[self buildUniversalLinkPresentationParameters]];
|
||||
universalLink:universalLink
|
||||
presentationParameters:screenParameters];
|
||||
return [self handleUniversalLinkWithParameters:parameters];
|
||||
}
|
||||
|
||||
|
|
|
@ -63,16 +63,6 @@ typedef NS_ENUM(NSUInteger, MasterTabBarIndex) {
|
|||
*/
|
||||
- (void)showOnboardingFlow;
|
||||
|
||||
/**
|
||||
Display the onboarding flow in order to pursue a registration process by using a predefined set
|
||||
of parameters.
|
||||
|
||||
If the provided registration parameters are not supported, the default onboarding flow will be used.
|
||||
|
||||
@param parameters the set of parameters.
|
||||
*/
|
||||
- (void)showOnboardingFlowWithRegistrationParameters:(NSDictionary*)parameters;
|
||||
|
||||
/**
|
||||
Display the onboarding flow configured to log back into a soft logout session.
|
||||
|
||||
|
|
|
@ -67,8 +67,6 @@
|
|||
@property (nonatomic, readwrite) id addAccountObserver;
|
||||
@property (nonatomic, readwrite) id removeAccountObserver;
|
||||
|
||||
// The parameters to pass to the Authentication view controller.
|
||||
@property (nonatomic, readwrite) NSDictionary *authViewControllerRegistrationParameters;
|
||||
@property (nonatomic, readwrite) MXCredentials *softLogoutCredentials;
|
||||
|
||||
@property (nonatomic) BOOL reviewSessionAlertHasBeenDisplayed;
|
||||
|
@ -478,12 +476,6 @@
|
|||
- (void)presentOnboardingFlow
|
||||
{
|
||||
OnboardingCoordinatorBridgePresenterParameters *parameters = [[OnboardingCoordinatorBridgePresenterParameters alloc] init];
|
||||
// Forward parameters if any
|
||||
if (self.authViewControllerRegistrationParameters)
|
||||
{
|
||||
parameters.externalRegistrationParameters = self.authViewControllerRegistrationParameters;
|
||||
self.authViewControllerRegistrationParameters = nil;
|
||||
}
|
||||
if (self.softLogoutCredentials)
|
||||
{
|
||||
parameters.softLogoutCredentials = self.softLogoutCredentials;
|
||||
|
@ -547,36 +539,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Sets up authentication with parameters detected in a universal link. For example
|
||||
https://app.element.io/#/register/?hs_url=matrix.example.com&is_url=identity.example.com
|
||||
*/
|
||||
|
||||
- (void)showOnboardingFlowWithRegistrationParameters:(NSDictionary *)parameters
|
||||
{
|
||||
if (self.onboardingCoordinatorBridgePresenter)
|
||||
{
|
||||
MXLogDebug(@"[MasterTabBarController] Universal link: Forward registration parameter to the existing AuthViewController");
|
||||
[self.onboardingCoordinatorBridgePresenter updateWithExternalRegistrationParameters:parameters];
|
||||
}
|
||||
else
|
||||
{
|
||||
MXLogDebug(@"[MasterTabBarController] Universal link: Prompt to logout current sessions and open AuthViewController to complete the registration");
|
||||
|
||||
// Keep a ref on the params
|
||||
self.authViewControllerRegistrationParameters = parameters;
|
||||
|
||||
// Prompt to logout. It will then display AuthViewController if the user is logged out.
|
||||
[[AppDelegate theDelegate] logoutWithConfirmation:YES completion:^(BOOL isLoggedOut) {
|
||||
if (!isLoggedOut)
|
||||
{
|
||||
// Reset temporary params
|
||||
self.authViewControllerRegistrationParameters = nil;
|
||||
}
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)showSoftLogoutOnboardingFlowWithCredentials:(MXCredentials*)credentials;
|
||||
{
|
||||
MXLogDebug(@"[MasterTabBarController] showAuthenticationScreenAfterSoftLogout");
|
||||
|
|
|
@ -52,6 +52,7 @@
|
|||
#import "BubbleRoomTimelineCellProvider.h"
|
||||
#import "RoomSelectedStickerBubbleCell.h"
|
||||
#import "MXRoom+Riot.h"
|
||||
#import "UniversalLink.h"
|
||||
|
||||
// MatrixKit common imports, shared with all targets
|
||||
#import "MatrixKit-Bridging-Header.h"
|
||||
|
|
|
@ -18,17 +18,27 @@
|
|||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface UniversalLink : NSObject
|
||||
@interface UniversalLink : NSObject <NSCopying>
|
||||
|
||||
/// Original url
|
||||
@property (nonatomic, copy, readonly) NSURL *url;
|
||||
|
||||
/// Path params from the link.
|
||||
@property (nonatomic, copy, readonly) NSArray<NSString*> *pathParams;
|
||||
|
||||
@property (nonatomic, copy, readonly) NSDictionary<NSString*, NSString*> *queryParams;
|
||||
/// Query params from the link. Does not conform to RFC 1808. Designed for simplicity.
|
||||
@property (nonatomic, copy, readonly) NSDictionary<NSString*, id> *queryParams;
|
||||
|
||||
- (id)initWithUrl:(NSURL *)url
|
||||
pathParams:(NSArray<NSString*> *)pathParams
|
||||
queryParams:(NSDictionary<NSString*, NSString*> *)queryParams;
|
||||
/// Homeserver url in the link if any
|
||||
@property (nonatomic, copy, readonly, nullable) NSString *homeserverUrl;
|
||||
/// Identity server url in the link if any
|
||||
@property (nonatomic, copy, readonly, nullable) NSString *identityServerUrl;
|
||||
/// via parameters url in the link if any
|
||||
@property (nonatomic, copy, readonly) NSArray<NSString*> *via;
|
||||
|
||||
/// Initializer
|
||||
/// @param url original url
|
||||
- (id)initWithUrl:(NSURL *)url;
|
||||
|
||||
@end
|
||||
|
||||
|
|
|
@ -15,21 +15,137 @@
|
|||
*/
|
||||
|
||||
#import "UniversalLink.h"
|
||||
#import "NSArray+Element.h"
|
||||
|
||||
@implementation UniversalLink
|
||||
|
||||
- (id)initWithUrl:(NSURL *)url pathParams:(NSArray<NSString *> *)pathParams queryParams:(NSDictionary<NSString *,NSString *> *)queryParams
|
||||
- (id)initWithUrl:(NSURL *)url
|
||||
{
|
||||
self = [super init];
|
||||
if (self)
|
||||
{
|
||||
_url = url;
|
||||
_pathParams = pathParams;
|
||||
_queryParams = queryParams;
|
||||
|
||||
// Extract required parameters from the link
|
||||
[self parsePathAndQueryParams];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
Extract params from the URL fragment part (after '#') of a vector.im Universal link:
|
||||
|
||||
The fragment can contain a '?'. So there are two kinds of parameters: path params and query params.
|
||||
It is in the form of /[pathParam1]/[pathParam2]?[queryParam1Key]=[queryParam1Value]&[queryParam2Key]=[queryParam2Value]
|
||||
*/
|
||||
- (void)parsePathAndQueryParams
|
||||
{
|
||||
NSArray<NSString*> *pathParams;
|
||||
NSMutableDictionary *queryParams = [NSMutableDictionary dictionary];
|
||||
|
||||
NSArray<NSString*> *fragments = [_url.fragment componentsSeparatedByString:@"?"];
|
||||
|
||||
// Extract path params
|
||||
pathParams = [fragments[0] componentsSeparatedByString:@"/"];
|
||||
|
||||
// Remove the first empty path param string
|
||||
pathParams = [pathParams filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"length > 0"]];
|
||||
|
||||
// URL decode each path param
|
||||
pathParams = [pathParams vc_map:^id _Nonnull(NSString * _Nonnull item) {
|
||||
return [item stringByRemovingPercentEncoding];
|
||||
}];
|
||||
|
||||
// Extract query params
|
||||
NSURLComponents *components = [NSURLComponents componentsWithURL:_url resolvingAgainstBaseURL:NO];
|
||||
for (NSURLQueryItem *item in components.queryItems)
|
||||
{
|
||||
if (item.value)
|
||||
{
|
||||
NSString *key = item.name;
|
||||
NSString *value = item.value;
|
||||
value = [value stringByReplacingOccurrencesOfString:@"+" withString:@" "];
|
||||
value = [value stringByRemovingPercentEncoding];
|
||||
|
||||
if ([key isEqualToString:@"via"])
|
||||
{
|
||||
// Special case the via parameter
|
||||
// As we can have several of them, store each value into an array
|
||||
if (!queryParams[key])
|
||||
{
|
||||
queryParams[key] = [NSMutableArray array];
|
||||
}
|
||||
|
||||
[queryParams[key] addObject:value];
|
||||
}
|
||||
else
|
||||
{
|
||||
queryParams[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Query params are in the form [queryParam1Key]=[queryParam1Value], so the
|
||||
// presence of at least one '=' character is mandatory
|
||||
if (fragments.count == 2 && (NSNotFound != [fragments[1] rangeOfString:@"="].location))
|
||||
{
|
||||
for (NSString *keyValue in [fragments[1] componentsSeparatedByString:@"&"])
|
||||
{
|
||||
// Get the parameter name
|
||||
NSString *key = [keyValue componentsSeparatedByString:@"="][0];
|
||||
|
||||
// Get the parameter value
|
||||
NSString *value = [keyValue componentsSeparatedByString:@"="][1];
|
||||
if (value.length)
|
||||
{
|
||||
value = [value stringByReplacingOccurrencesOfString:@"+" withString:@" "];
|
||||
value = [value stringByRemovingPercentEncoding];
|
||||
|
||||
if ([key isEqualToString:@"via"])
|
||||
{
|
||||
// Special case the via parameter
|
||||
// As we can have several of them, store each value into an array
|
||||
if (!queryParams[key])
|
||||
{
|
||||
queryParams[key] = [NSMutableArray array];
|
||||
}
|
||||
|
||||
if (![queryParams[key] containsObject:value])
|
||||
{
|
||||
[queryParams[key] addObject:value];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
queryParams[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_pathParams = pathParams;
|
||||
_queryParams = queryParams;
|
||||
}
|
||||
|
||||
- (NSString *)homeserverUrl
|
||||
{
|
||||
return _queryParams[@"hs_url"];
|
||||
}
|
||||
|
||||
- (NSString *)identityServerUrl
|
||||
{
|
||||
return _queryParams[@"is_url"];
|
||||
}
|
||||
|
||||
- (NSArray<NSString *> *)via
|
||||
{
|
||||
NSArray<NSString *> *result = _queryParams[@"via"];
|
||||
if (!result)
|
||||
{
|
||||
return @[];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)other
|
||||
{
|
||||
if (other == self)
|
||||
|
@ -57,4 +173,21 @@
|
|||
return result;
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return [NSString stringWithFormat:@"<UniversalLink: %@>", _url.absoluteString];
|
||||
}
|
||||
|
||||
#pragma mark - NSCopying
|
||||
- (id)copyWithZone:(NSZone *)zone
|
||||
{
|
||||
UniversalLink *link = [[self.class allocWithZone:zone] init];
|
||||
|
||||
link->_url = [_url copyWithZone:zone];
|
||||
link->_pathParams = [_pathParams copyWithZone:zone];
|
||||
link->_queryParams = [_queryParams copyWithZone:zone];
|
||||
|
||||
return link;
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -25,8 +25,12 @@ protocol AuthenticationServiceDelegate: AnyObject {
|
|||
/// - transactionID: The transaction ID generated during SSO page presentation.
|
||||
/// - Returns: `true` if the SSO login can be continued.
|
||||
func authenticationService(_ service: AuthenticationService, didReceive ssoLoginToken: String, with transactionID: String) -> Bool
|
||||
|
||||
func authenticationService(_ service: AuthenticationService,
|
||||
didUpdateStateWithLink link: UniversalLink)
|
||||
}
|
||||
|
||||
@objcMembers
|
||||
class AuthenticationService: NSObject {
|
||||
|
||||
/// The shared service object.
|
||||
|
@ -73,7 +77,40 @@ class AuthenticationService: NSObject {
|
|||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
|
||||
/// Parse and handle a server provisioning link.
|
||||
/// - Parameter universalLink: A link such as https://mobile.element.io/?hs_url=matrix.example.com&is_url=identity.example.com
|
||||
/// - Returns: `true` if a provisioning link was detected and handled.
|
||||
@discardableResult
|
||||
func handleServerProvisioningLink(_ universalLink: UniversalLink) -> Bool {
|
||||
MXLog.debug("[AuthenticationService] handleServerProvisioningLink: \(universalLink)")
|
||||
|
||||
let hsUrl = universalLink.homeserverUrl
|
||||
let isUrl = universalLink.identityServerUrl
|
||||
|
||||
if hsUrl == nil && isUrl == nil {
|
||||
MXLog.debug("[AuthenticationService] handleServerProvisioningLink: no hsUrl or isUrl")
|
||||
return false
|
||||
}
|
||||
|
||||
let isRegister = universalLink.pathParams.first == "register"
|
||||
let flow: AuthenticationFlow = isRegister ? .register : .login
|
||||
|
||||
if needsAuthentication {
|
||||
reset()
|
||||
// not logged in
|
||||
// update the state with given HS and IS addresses
|
||||
state = AuthenticationState(flow: flow,
|
||||
homeserverAddress: hsUrl ?? BuildSettings.serverConfigDefaultHomeserverUrlString,
|
||||
identityServer: isUrl ?? BuildSettings.serverConfigDefaultIdentityServerUrlString)
|
||||
delegate?.authenticationService(self, didUpdateStateWithLink: universalLink)
|
||||
} else {
|
||||
// logged in
|
||||
AppDelegate.theDelegate().displayLogoutConfirmation(for: universalLink, completion: nil)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/// Whether authentication is needed by checking for any accounts.
|
||||
/// - Returns: `true` there are no accounts or if there is an inactive account that has had a soft logout.
|
||||
var needsAuthentication: Bool {
|
||||
|
@ -145,7 +182,10 @@ class AuthenticationService: NSObject {
|
|||
|
||||
// The previously used homeserver is re-used as `startFlow` will be called again a replace it anyway.
|
||||
let address = state.homeserver.addressFromUser ?? state.homeserver.address
|
||||
self.state = AuthenticationState(flow: .login, homeserverAddress: address)
|
||||
let identityServer = state.identityServer
|
||||
self.state = AuthenticationState(flow: .login,
|
||||
homeserverAddress: address,
|
||||
identityServer: identityServer)
|
||||
}
|
||||
|
||||
/// Continues an SSO flow when completion comes via a deep link.
|
||||
|
|
|
@ -23,16 +23,20 @@ struct AuthenticationState {
|
|||
|
||||
/// Information about the currently selected homeserver.
|
||||
var homeserver: Homeserver
|
||||
/// Currently selected identity server
|
||||
var identityServer: String?
|
||||
var isForceLoginFallbackEnabled = false
|
||||
|
||||
init(flow: AuthenticationFlow, homeserverAddress: String) {
|
||||
init(flow: AuthenticationFlow, homeserverAddress: String, identityServer: String? = nil) {
|
||||
self.flow = flow
|
||||
self.homeserver = Homeserver(address: homeserverAddress)
|
||||
self.identityServer = identityServer
|
||||
}
|
||||
|
||||
init(flow: AuthenticationFlow, homeserver: Homeserver) {
|
||||
init(flow: AuthenticationFlow, homeserver: Homeserver, identityServer: String? = nil) {
|
||||
self.flow = flow
|
||||
self.homeserver = homeserver
|
||||
self.identityServer = identityServer
|
||||
}
|
||||
|
||||
struct Homeserver {
|
||||
|
|
98
RiotTests/UniversalLinkTests.swift
Normal file
98
RiotTests/UniversalLinkTests.swift
Normal file
|
@ -0,0 +1,98 @@
|
|||
//
|
||||
// Copyright 2022 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 XCTest
|
||||
@testable import Riot
|
||||
|
||||
class UniversalLinkTests: XCTestCase {
|
||||
|
||||
enum UniversalLinkTestError: Error {
|
||||
case invalidURL
|
||||
}
|
||||
|
||||
func testInitialization() throws {
|
||||
guard let url = URL(string: "https://example.com") else {
|
||||
throw UniversalLinkTestError.invalidURL
|
||||
}
|
||||
let universalLink = UniversalLink(url: url)
|
||||
XCTAssertEqual(universalLink.url, url)
|
||||
XCTAssertTrue(universalLink.pathParams.isEmpty)
|
||||
XCTAssertTrue(universalLink.queryParams.isEmpty)
|
||||
}
|
||||
|
||||
func testRegistrationLink() throws {
|
||||
guard let url = URL(string: "https://app.element.io/#/register/?hs_url=matrix.example.com&is_url=identity.example.com") else {
|
||||
throw UniversalLinkTestError.invalidURL
|
||||
}
|
||||
let universalLink = UniversalLink(url: url)
|
||||
XCTAssertEqual(universalLink.url, url)
|
||||
XCTAssertEqual(universalLink.pathParams.count, 1)
|
||||
XCTAssertEqual(universalLink.pathParams.first, "register")
|
||||
XCTAssertEqual(universalLink.queryParams.count, 2)
|
||||
XCTAssertEqual(universalLink.homeserverUrl, "matrix.example.com")
|
||||
XCTAssertEqual(universalLink.identityServerUrl, "identity.example.com")
|
||||
}
|
||||
|
||||
func testLoginLink() throws {
|
||||
guard let url = URL(string: "https://mobile.element.io/?hs_url=matrix.example.com&is_url=identity.example.com") else {
|
||||
throw UniversalLinkTestError.invalidURL
|
||||
}
|
||||
let universalLink = UniversalLink(url: url)
|
||||
XCTAssertEqual(universalLink.url, url)
|
||||
XCTAssertTrue(universalLink.pathParams.isEmpty)
|
||||
XCTAssertEqual(universalLink.queryParams.count, 2)
|
||||
XCTAssertEqual(universalLink.homeserverUrl, "matrix.example.com")
|
||||
XCTAssertEqual(universalLink.identityServerUrl, "identity.example.com")
|
||||
}
|
||||
|
||||
func testPathParams() throws {
|
||||
guard let url = URL(string: "https://mobile.element.io/#/param1/param2/param3?hs_url=matrix.example.com&is_url=identity.example.com") else {
|
||||
throw UniversalLinkTestError.invalidURL
|
||||
}
|
||||
let universalLink = UniversalLink(url: url)
|
||||
XCTAssertEqual(universalLink.url, url)
|
||||
XCTAssertEqual(universalLink.pathParams.count, 3)
|
||||
XCTAssertEqual(universalLink.pathParams[0], "param1")
|
||||
XCTAssertEqual(universalLink.pathParams[1], "param2")
|
||||
XCTAssertEqual(universalLink.pathParams[2], "param3")
|
||||
XCTAssertEqual(universalLink.queryParams.count, 2)
|
||||
XCTAssertEqual(universalLink.homeserverUrl, "matrix.example.com")
|
||||
XCTAssertEqual(universalLink.identityServerUrl, "identity.example.com")
|
||||
}
|
||||
|
||||
func testVia() throws {
|
||||
guard let url = URL(string: "https://mobile.element.io/?hs_url=matrix.example.com&is_url=identity.example.com&via=param1&via=param2") else {
|
||||
throw UniversalLinkTestError.invalidURL
|
||||
}
|
||||
let universalLink = UniversalLink(url: url)
|
||||
XCTAssertEqual(universalLink.url, url)
|
||||
XCTAssertEqual(universalLink.queryParams.count, 3)
|
||||
XCTAssertEqual(universalLink.homeserverUrl, "matrix.example.com")
|
||||
XCTAssertEqual(universalLink.identityServerUrl, "identity.example.com")
|
||||
XCTAssertEqual(universalLink.via, ["param1", "param2"])
|
||||
}
|
||||
|
||||
func testDescription() throws {
|
||||
let str = "https://mobile.element.io/?hs_url=matrix.example.com&is_url=identity.example.com&via=param1&via=param2"
|
||||
guard let url = URL(string: str) else {
|
||||
throw UniversalLinkTestError.invalidURL
|
||||
}
|
||||
let universalLink = UniversalLink(url: url)
|
||||
let desc = String(format: "<UniversalLink: %@>", str)
|
||||
XCTAssertEqual(universalLink.description, desc)
|
||||
}
|
||||
|
||||
}
|
1
changelog.d/6180.change
Normal file
1
changelog.d/6180.change
Normal file
|
@ -0,0 +1 @@
|
|||
FTUE: Support server provisioning links in the authentication flow.
|
Loading…
Reference in a new issue