2014-11-24 09:38:23 +00:00
/ *
Copyright 2014 OpenMarket 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 .
* /
2015-02-05 10:43:06 +00:00
# import "AuthenticationViewController.h"
2014-11-24 09:38:23 +00:00
2015-01-23 14:36:05 +00:00
# import "MatrixSDKHandler.h"
2014-11-24 09:38:23 +00:00
# import "AppDelegate.h"
2015-01-23 12:46:27 +00:00
# import "MXCAlert.h"
2014-11-24 09:38:23 +00:00
2015-02-10 15:12:58 +00:00
@ interface AuthenticationViewController ( ) {
// Current request in progress
NSOperation * mxAuthFlowRequest ;
// Array of flows supported by the home server and implemented by the app ( for the current auth type )
NSMutableArray * supportedFlows ;
// The current view in which auth inputs are displayed
AuthInputsView * currentAuthInputsView ;
2014-12-18 08:32:29 +00:00
// reference to any opened alert view
2015-01-23 12:46:27 +00:00
MXCAlert * alert ;
2014-12-18 08:32:29 +00:00
}
2015-02-10 15:12:58 +00:00
// Return true if the provided flow ( kMXLoginFlowType ) is supported by the application
+ ( BOOL ) isImplementedFlowType : ( NSString * ) flowType ;
// The current authentication type
@ property ( nonatomic ) AuthenticationType authType ;
@ property ( nonatomic ) MXLoginFlow * selectedFlow ;
2014-11-24 09:38:23 +00:00
@ property ( strong , nonatomic ) IBOutlet UIScrollView * scrollView ;
@ property ( weak , nonatomic ) IBOutlet UIView * contentView ;
2015-02-10 15:12:58 +00:00
@ property ( weak , nonatomic ) IBOutlet NSLayoutConstraint * contentViewHeightConstraint ;
@ property ( weak , nonatomic ) IBOutlet UILabel * createAccountLabel ;
@ property ( weak , nonatomic ) IBOutlet UIView * authInputsContainerView ;
@ property ( weak , nonatomic ) IBOutlet NSLayoutConstraint * authInputContainerViewHeightConstraint ;
@ property ( weak , nonatomic ) IBOutlet AuthInputsPasswordBasedView * authInputsPasswordBasedView ;
@ property ( weak , nonatomic ) IBOutlet AuthInputsEmailCodeBasedView * authInputsEmailCodeBasedView ;
2014-11-24 09:38:23 +00:00
@ property ( weak , nonatomic ) IBOutlet UITextField * homeServerTextField ;
2015-02-10 15:12:58 +00:00
@ property ( weak , nonatomic ) IBOutlet UILabel * homeServerInfoLabel ;
@ property ( weak , nonatomic ) IBOutlet UITextField * identityServerTextField ;
@ property ( weak , nonatomic ) IBOutlet UILabel * identityServerInfoLabel ;
2014-11-24 09:38:23 +00:00
2015-02-10 15:12:58 +00:00
@ property ( weak , nonatomic ) IBOutlet UIButton * submitButton ;
@ property ( weak , nonatomic ) IBOutlet UIButton * authSwitchButton ;
2014-11-24 09:38:23 +00:00
@ property ( strong , nonatomic ) IBOutlet UIActivityIndicatorView * activityIndicator ;
2015-02-10 15:12:58 +00:00
@ property ( weak , nonatomic ) IBOutlet UILabel * noFlowLabel ;
@ property ( weak , nonatomic ) IBOutlet UIButton * retryButton ;
2014-11-24 09:38:23 +00:00
@ end
2015-02-05 10:43:06 +00:00
@ implementation AuthenticationViewController
2014-11-24 09:38:23 +00:00
- ( void ) viewDidLoad {
[ super viewDidLoad ] ;
// Do any additional setup after loading the view , typically from a nib .
// Force contentView in full width
NSLayoutConstraint * leftConstraint = [ NSLayoutConstraint constraintWithItem : self . contentView
attribute : NSLayoutAttributeLeading
relatedBy : 0
toItem : self . view
attribute : NSLayoutAttributeLeft
multiplier : 1.0
constant : 0 ] ;
[ self . view addConstraint : leftConstraint ] ;
NSLayoutConstraint * rightConstraint = [ NSLayoutConstraint constraintWithItem : self . contentView
attribute : NSLayoutAttributeTrailing
relatedBy : 0
toItem : self . view
attribute : NSLayoutAttributeRight
multiplier : 1.0
constant : 0 ] ;
[ self . view addConstraint : rightConstraint ] ;
2015-02-10 15:12:58 +00:00
_scrollView . autoresizingMask = UIViewAutoresizingFlexibleWidth ;
_submitButton . enabled = NO ;
_authSwitchButton . enabled = YES ;
_authInputsPasswordBasedView . delegate = self ;
_authInputsEmailCodeBasedView . delegate = self ;
supportedFlows = [ NSMutableArray array ] ;
2015-01-23 14:36:05 +00:00
_homeServerTextField . text = [ [ MatrixSDKHandler sharedHandler ] homeServerURL ] ;
2015-02-10 15:12:58 +00:00
_identityServerTextField . text = [ [ MatrixSDKHandler sharedHandler ] identityServerURL ] ;
// Set default auth type
dispatch_async ( dispatch_get _main _queue ( ) , ^ {
self . authType = AuthenticationTypeLogin ;
} ) ;
}
- ( void ) dealloc {
supportedFlows = nil ;
if ( mxAuthFlowRequest ) {
[ mxAuthFlowRequest cancel ] ;
mxAuthFlowRequest = nil ;
}
2014-11-24 09:38:23 +00:00
}
- ( void ) didReceiveMemoryWarning {
[ super didReceiveMemoryWarning ] ;
// Dispose of any resources that can be recreated .
}
- ( void ) viewWillAppear : ( BOOL ) animated {
[ super viewWillAppear : animated ] ;
2015-02-10 15:12:58 +00:00
// Update supported authentication flow
[ self refreshSupportedAuthFlow ] ;
2014-11-24 09:38:23 +00:00
[ [ NSNotificationCenter defaultCenter ] addObserver : self selector : @ selector ( onKeyboardWillShow : ) name : UIKeyboardWillShowNotification object : nil ] ;
[ [ NSNotificationCenter defaultCenter ] addObserver : self selector : @ selector ( onKeyboardWillHide : ) name : UIKeyboardWillHideNotification object : nil ] ;
[ [ NSNotificationCenter defaultCenter ] addObserver : self selector : @ selector ( onTextFieldChange : ) name : UITextFieldTextDidChangeNotification object : nil ] ;
}
- ( void ) viewWillDisappear : ( BOOL ) animated {
[ super viewWillDisappear : animated ] ;
2015-02-10 15:12:58 +00:00
[ self dismissKeyboard ] ;
2014-12-18 08:32:29 +00:00
// close any opened alert
if ( alert ) {
[ alert dismiss : NO ] ;
alert = nil ;
}
2015-02-10 15:12:58 +00:00
[ [ NSNotificationCenter defaultCenter ] removeObserver : self name : AFNetworkingReachabilityDidChangeNotification object : nil ] ;
2014-11-24 09:38:23 +00:00
[ [ NSNotificationCenter defaultCenter ] removeObserver : self name : UIKeyboardWillShowNotification object : nil ] ;
[ [ NSNotificationCenter defaultCenter ] removeObserver : self name : UIKeyboardWillHideNotification object : nil ] ;
[ [ NSNotificationCenter defaultCenter ] removeObserver : self name : UITextFieldTextDidChangeNotification object : nil ] ;
}
2015-02-10 15:12:58 +00:00
# pragma mark -
+ ( BOOL ) isImplementedFlowType : ( NSString * ) flowType {
if ( [ flowType isEqualToString : kMXLoginFlowTypePassword ] || [ flowType isEqualToString : kMXLoginFlowTypeEmailCode ] ) {
return YES ;
}
return NO ;
}
- ( void ) setAuthType : ( AuthenticationType ) authType {
if ( authType = = AuthenticationTypeLogin ) {
_createAccountLabel . hidden = YES ;
[ _submitButton setTitle : @ "Login" forState : UIControlStateNormal ] ;
[ _submitButton setTitle : @ "Login" forState : UIControlStateHighlighted ] ;
[ _authSwitchButton setTitle : @ "Create account" forState : UIControlStateNormal ] ;
[ _authSwitchButton setTitle : @ "Create account" forState : UIControlStateHighlighted ] ;
} else {
_createAccountLabel . hidden = NO ;
[ _submitButton setTitle : @ "Sign up" forState : UIControlStateNormal ] ;
[ _submitButton setTitle : @ "Sign up" forState : UIControlStateHighlighted ] ;
[ _authSwitchButton setTitle : @ "Back" forState : UIControlStateNormal ] ;
[ _authSwitchButton setTitle : @ "Back" forState : UIControlStateHighlighted ] ;
}
_authType = authType ;
// Update supported authentication flow
[ self refreshSupportedAuthFlow ] ;
}
- ( void ) setSelectedFlow : ( MXLoginFlow * ) selectedFlow {
// Hide views which depend on auth flow
_submitButton . hidden = YES ;
_authInputsPasswordBasedView . hidden = YES ;
_authInputsEmailCodeBasedView . hidden = YES ;
_noFlowLabel . hidden = YES ;
_retryButton . hidden = YES ;
currentAuthInputsView = nil ;
// Select the right auth inputs view
if ( [ selectedFlow . type isEqualToString : kMXLoginFlowTypePassword ] ) {
currentAuthInputsView = _authInputsPasswordBasedView ;
} else if ( [ selectedFlow . type isEqualToString : kMXLoginFlowTypeEmailCode ] ) {
currentAuthInputsView = _authInputsEmailCodeBasedView ;
}
if ( currentAuthInputsView ) {
_submitButton . hidden = NO ;
currentAuthInputsView . hidden = NO ;
currentAuthInputsView . authType = _authType ;
_authInputContainerViewHeightConstraint . constant = currentAuthInputsView . actualHeight ;
} else {
// No input fields are displayed
_authInputContainerViewHeightConstraint . constant = 80 ;
}
[ self . view layoutIfNeeded ] ;
// Refresh content view height
_contentViewHeightConstraint . constant = _authSwitchButton . frame . origin . y + _authSwitchButton . frame . size . height + 15 ;
_selectedFlow = selectedFlow ;
}
- ( void ) setUserInteractionEnabled : ( BOOL ) isEnabled {
_submitButton . enabled = ( isEnabled && currentAuthInputsView . areAllRequiredFieldsFilled && _homeServerTextField . text . length ) ;
_authSwitchButton . enabled = isEnabled ;
_homeServerTextField . enabled = isEnabled ;
_identityServerTextField . enabled = isEnabled ;
}
- ( void ) refreshSupportedAuthFlow {
MatrixSDKHandler * mxHandler = [ MatrixSDKHandler sharedHandler ] ;
2015-02-12 10:16:28 +00:00
// Remove reachability observer
2015-02-10 15:12:58 +00:00
[ [ NSNotificationCenter defaultCenter ] removeObserver : self name : AFNetworkingReachabilityDidChangeNotification object : nil ] ;
// Cancel protential request in progress
[ mxAuthFlowRequest cancel ] ;
mxAuthFlowRequest = nil ;
[ _activityIndicator startAnimating ] ;
self . selectedFlow = nil ;
if ( _authType = = AuthenticationTypeLogin ) {
mxAuthFlowRequest = [ mxHandler . mxRestClient getLoginFlow : ^ ( NSArray * flows ) {
[ self handleHomeServerFlows : flows ] ;
} failure : ^ ( NSError * error ) {
[ self onFailureDuringFlowRefresh : error ] ;
} ] ;
} else {
mxAuthFlowRequest = [ mxHandler . mxRestClient getRegisterFlow : ^ ( NSArray * flows ) {
[ self handleHomeServerFlows : flows ] ;
} failure : ^ ( NSError * error ) {
[ self onFailureDuringFlowRefresh : error ] ;
} ] ;
}
}
- ( void ) handleHomeServerFlows : ( NSArray * ) flows {
[ _activityIndicator stopAnimating ] ;
[ supportedFlows removeAllObjects ] ;
for ( MXLoginFlow * flow in flows ) {
if ( [ AuthenticationViewController isImplementedFlowType : flow . type ] ) {
[ supportedFlows addObject : flow ] ;
}
}
if ( supportedFlows . count ) {
// FIXME display supported flows
// Currently we select password based auth
for ( MXLoginFlow * flow in supportedFlows ) {
if ( [ flow . type isEqualToString : kMXLoginFlowTypePassword ] ) {
self . selectedFlow = flow ;
break ;
} else if ( [ flow . type isEqualToString : kMXLoginFlowTypeEmailCode ] ) {
self . selectedFlow = flow ;
break ;
}
}
}
if ( ! _selectedFlow ) {
// Notify user that no flow is supported
_noFlowLabel . text = [ NSString stringWithFormat : @ "Currently we do not support %@ flows defined by this Home Server" , _authType = = AuthenticationTypeLogin ? @ "Login" : @ "Registration" ] ;
_noFlowLabel . hidden = NO ;
_retryButton . hidden = NO ;
}
}
- ( void ) onFailureDuringFlowRefresh : ( NSError * ) error {
if ( [ error . domain isEqualToString : NSURLErrorDomain ] && error . code = = kCFURLErrorCancelled ) {
// Ignore this error
return ;
}
2015-02-12 10:16:28 +00:00
[ _activityIndicator stopAnimating ] ;
2015-02-10 15:12:58 +00:00
NSLog ( @ "GET auth flows failed: %@" , error ) ;
// Alert user
NSString * title = [ error . userInfo valueForKey : NSLocalizedFailureReasonErrorKey ] ;
if ( ! title )
{
title = @ "Error" ;
}
NSString * msg = [ error . userInfo valueForKey : NSLocalizedDescriptionKey ] ;
alert = [ [ MXCAlert alloc ] initWithTitle : title message : msg style : MXCAlertStyleAlert ] ;
alert . cancelButtonIndex = [ alert addActionWithTitle : @ "Dismiss" style : MXCAlertActionStyleDefault handler : ^ ( MXCAlert * alert ) { } ] ;
[ alert showInViewController : self ] ;
// Display failure reason
_noFlowLabel . hidden = NO ;
_noFlowLabel . text = [ error . userInfo valueForKey : NSLocalizedDescriptionKey ] ;
if ( ! _noFlowLabel . text . length ) {
_noFlowLabel . text = @ "We failed to retrieve authentication flow from this Home Server" ;
}
_retryButton . hidden = NO ;
// Handle specific error code here
if ( [ error . domain isEqualToString : NSURLErrorDomain ] ) {
// Check network reachability
if ( error . code = = NSURLErrorNotConnectedToInternet ) {
2015-02-12 10:16:28 +00:00
// Add reachability observer in order to launch a new request when network will be available
2015-02-10 15:12:58 +00:00
[ [ NSNotificationCenter defaultCenter ] addObserver : self selector : @ selector ( onReachabilityStatusChange : ) name : AFNetworkingReachabilityDidChangeNotification object : nil ] ;
} else if ( error . code = = kCFURLErrorTimedOut ) {
// Send a new request in 2 sec
dispatch_after ( dispatch_time ( DISPATCH_TIME _NOW , ( int64_t ) ( 2 * NSEC_PER _SEC ) ) , dispatch_get _main _queue ( ) , ^ {
[ self refreshSupportedAuthFlow ] ;
} ) ;
}
}
}
- ( void ) onReachabilityStatusChange : ( NSNotification * ) notif {
AFNetworkReachabilityManager * reachabilityManager = [ AFNetworkReachabilityManager sharedManager ] ;
AFNetworkReachabilityStatus status = reachabilityManager . networkReachabilityStatus ;
if ( status = = AFNetworkReachabilityStatusReachableViaWiFi || status = = AFNetworkReachabilityStatusReachableViaWWAN ) {
dispatch_async ( dispatch_get _main _queue ( ) , ^ {
[ self refreshSupportedAuthFlow ] ;
} ) ;
} else if ( status = = AFNetworkReachabilityStatusNotReachable ) {
_noFlowLabel . text = @ "Please check your network connectivity" ;
}
}
- ( IBAction ) onButtonPressed : ( id ) sender {
[ self dismissKeyboard ] ;
if ( sender = = _submitButton ) {
MatrixSDKHandler * matrix = [ MatrixSDKHandler sharedHandler ] ;
if ( matrix . mxRestClient ) {
// Disable user interaction to prevent multiple requests
[ self setUserInteractionEnabled : NO ] ;
[ _activityIndicator startAnimating ] ;
if ( _authType = = AuthenticationTypeLogin ) {
if ( [ _selectedFlow . type isEqualToString : kMXLoginFlowTypePassword ] ) {
[ matrix . mxRestClient loginWithUser : matrix . userLogin andPassword : _authInputsPasswordBasedView . passWordTextField . text
success : ^ ( MXCredentials * credentials ) {
[ _activityIndicator stopAnimating ] ;
// Report credentials
[ matrix setUserId : credentials . userId ] ;
[ matrix setAccessToken : credentials . accessToken ] ;
// Extract homeServer name from userId
NSArray * components = [ credentials . userId componentsSeparatedByString : @ ":" ] ;
if ( components . count = = 2 ) {
[ matrix setHomeServer : [ components lastObject ] ] ;
} else {
NSLog ( @ "Unexpected error: the userId is not correctly formatted: %@" , credentials . userId ) ;
}
[ self dismissViewControllerAnimated : YES completion : nil ] ;
}
failure : ^ ( NSError * error ) {
[ self onFailureDuringAuthRequest : error ] ;
} ] ;
} else {
// FIXME
[ self onFailureDuringAuthRequest : [ NSError errorWithDomain : nil code : 0 userInfo : @ { @ "error" : @ "Not supported yet" } ] ] ;
}
} else {
// FIXME
[ self onFailureDuringAuthRequest : [ NSError errorWithDomain : nil code : 0 userInfo : @ { @ "error" : @ "Not supported yet" } ] ] ;
}
}
} else if ( sender = = _authSwitchButton ) {
if ( _authType = = AuthenticationTypeLogin ) {
self . authType = AuthenticationTypeRegister ;
} else {
self . authType = AuthenticationTypeLogin ;
}
} else if ( sender = = _retryButton ) {
[ self refreshSupportedAuthFlow ] ;
}
}
- ( void ) onFailureDuringAuthRequest : ( NSError * ) error {
[ _activityIndicator stopAnimating ] ;
[ self setUserInteractionEnabled : YES ] ;
NSLog ( @ "Auth request failed: %@" , error ) ;
// translate the error code to a human message
NSString * message = error . localizedDescription ;
NSDictionary * dict = error . userInfo ;
// detect if it is a Matrix SDK issue
if ( dict ) {
NSString * localizedError = [ dict valueForKey : @ "error" ] ;
NSString * errCode = [ dict valueForKey : @ "errcode" ] ;
if ( errCode ) {
if ( [ errCode isEqualToString : @ "M_FORBIDDEN" ] ) {
message = @ "Invalid username/password" ;
} else if ( localizedError . length > 0 ) {
message = localizedError ;
} else if ( [ errCode isEqualToString : @ "M_UNKNOWN_TOKEN" ] ) {
message = @ "The access token specified was not recognised" ;
} else if ( [ errCode isEqualToString : @ "M_BAD_JSON" ] ) {
message = @ "Malformed JSON" ;
} else if ( [ errCode isEqualToString : @ "M_NOT_JSON" ] ) {
message = @ "Did not contain valid JSON" ;
} else if ( [ errCode isEqualToString : @ "M_LIMIT_EXCEEDED" ] ) {
message = @ "Too many requests have been sent" ;
} else if ( [ errCode isEqualToString : @ "M_USER_IN_USE" ] ) {
message = @ "This user name is already used" ;
} else if ( [ errCode isEqualToString : @ "M_LOGIN_EMAIL_URL_NOT_YET" ] ) {
message = @ "The email link which has not been clicked yet" ;
} else {
message = errCode ;
}
}
}
// Alert user
alert = [ [ MXCAlert alloc ] initWithTitle : @ "Login Failed" message : message style : MXCAlertStyleAlert ] ;
[ alert addActionWithTitle : @ "Dismiss" style : MXCAlertActionStyleCancel handler : ^ ( MXCAlert * alert ) { } ] ;
[ alert showInViewController : self ] ;
}
# pragma mark - Keyboard handling
2014-11-24 09:38:23 +00:00
- ( void ) onKeyboardWillShow : ( NSNotification * ) notif {
NSValue * rectVal = notif . userInfo [ UIKeyboardFrameEndUserInfoKey ] ;
CGRect endRect = rectVal . CGRectValue ;
UIEdgeInsets insets = self . scrollView . contentInset ;
// Handle portrait / landscape mode
insets . bottom = ( endRect . origin . y = = 0 ) ? endRect . size . width : endRect . size . height ;
self . scrollView . contentInset = insets ;
}
- ( void ) onKeyboardWillHide : ( NSNotification * ) notif {
UIEdgeInsets insets = self . scrollView . contentInset ;
insets . bottom = 0 ;
self . scrollView . contentInset = insets ;
}
2014-12-16 21:54:26 +00:00
- ( void ) dismissKeyboard {
2014-11-24 09:38:23 +00:00
// Hide the keyboard
2015-02-10 15:12:58 +00:00
[ currentAuthInputsView dismissKeyboard ] ;
2014-11-24 09:38:23 +00:00
[ _homeServerTextField resignFirstResponder ] ;
2015-02-10 15:12:58 +00:00
[ _identityServerTextField resignFirstResponder ] ;
2014-11-24 09:38:23 +00:00
}
# pragma mark - UITextField delegate
- ( void ) onTextFieldChange : ( NSNotification * ) notif {
NSString * homeServerURL = _homeServerTextField . text ;
2015-02-10 15:12:58 +00:00
if ( currentAuthInputsView . areAllRequiredFieldsFilled && homeServerURL . length ) {
_submitButton . enabled = YES ;
2014-11-24 09:38:23 +00:00
} else {
2015-02-10 15:12:58 +00:00
_submitButton . enabled = NO ;
2014-11-24 09:38:23 +00:00
}
}
2014-12-16 21:54:26 +00:00
- ( void ) textFieldDidEndEditing : ( UITextField * ) textField {
2015-02-10 15:12:58 +00:00
MatrixSDKHandler * mxHandler = [ MatrixSDKHandler sharedHandler ] ;
if ( textField = = _homeServerTextField ) {
if ( ! [ [ mxHandler homeServerURL ] isEqualToString : textField . text ] ) {
[ mxHandler setHomeServerURL : textField . text ] ;
if ( ! textField . text . length ) {
// Force refresh with default value
textField . text = [ mxHandler homeServerURL ] ;
}
// Refresh UI
[ self refreshSupportedAuthFlow ] ;
}
2014-11-24 09:38:23 +00:00
}
2015-02-10 15:12:58 +00:00
else if ( textField = = _identityServerTextField ) {
[ mxHandler setIdentityServerURL : textField . text ] ;
2014-12-16 21:54:26 +00:00
if ( ! textField . text . length ) {
// Force refresh with default value
2015-02-10 15:12:58 +00:00
textField . text = [ mxHandler identityServerURL ] ;
2014-12-16 21:54:26 +00:00
}
2014-11-24 09:38:23 +00:00
}
}
2015-02-10 15:12:58 +00:00
- ( BOOL ) textFieldShouldReturn : ( UITextField * ) textField {
if ( textField . returnKeyType = = UIReturnKeyDone ) {
2014-11-24 09:38:23 +00:00
// "Done" key has been pressed
[ textField resignFirstResponder ] ;
}
return YES ;
}
2015-02-10 15:12:58 +00:00
# pragma mark - AuthInputsViewDelegate delegate
2014-11-24 09:38:23 +00:00
2015-02-10 15:12:58 +00:00
- ( void ) authInputsDoneKeyHasBeenPressed : ( AuthInputsView * ) authInputsView {
if ( _submitButton . isEnabled ) {
// Launch authentication now
[ self onButtonPressed : _submitButton ] ;
2014-11-24 09:38:23 +00:00
}
}
@ end