Thursday, November 24, 2016

Understand oAuth authentication

Participants:
1. Resource owner, user who has credential to access the resource, like yourself.
2. Resource server, a web server where a resource is stored in there, like google drive.
3. Authorization server, an authentication server which manages the user identity, and authenticates the user, tokens issued by authorization server can be trusted by Resource server.
4. Client, application used by user (most likely resource owner) to work on the resource

Configuration:
clientID: "fb266000-544f-4db6-957e-1e05f530bb18",          
The client app must first register itself to authorization server to obtain a unique client id -- a unique string representing the registered client app. So when authorization request comes authorization server knows which client sends this request, Client id is not confidential.

grantType: "authorization_code"
Indicate the client type, confidential or public

authorizationEndpoint: "https://oauthasservices-wba2e8af2.int.sap.hana.ondemand.com/oauth2/api/v1/authorize",
Authorization endpoint - used by the client app to obtain authorization from the user. The user's identity will be authenticated at this url by user identity provider, the authentication method can be Basic, SAML, client cert, etc. This is the first step to establish oauth connection.

TokenEndpoint: "https://oauthasservices-wba2e8af2.int.sap.hana.ondemand.com/oauth2/api/v1/token",
Token endpoint - used by the client to exchange an authorization grant for an access token, typically with client id information.

redirectURL: "com.sap.fiori.client.debug://oauth2",
Redirection endpoint - used by the authorization server to return responses containing authorization credentials to the client via the resource owner user-agent.

OAuth authentication must be performed with TLS (https) connection, as token is passed in clear text.

Authentication process:
1. When client app needs to perform an oAuth authentication, it first needs to open a browser (or webview), and send the initial GET request to the authorization endpoint, the url also includes the parameter of
response_type (required)
client ID (required)
redirect url, 
scope
state
response_type should be set to "code" if grandType is set to "authorization_code". This request needs to be opened in a browser (or webView) user agent, so the web page can lead user to input his credential to finish the user authentication. The authorization server may validate the rediretURL with the value
registered in it to avoid sending the authorization code to malicious user agent.
Request sample:
https://server.sample.com/authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz
        &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb

2. When user authentication is succeeded, authorization server will send back 302 redirect request. The authorization code (or error) is included in the redirect url as "code" (or "error") parameter . The response url parameters include
code (required)
state (required)

example:
HTTP/1.1 302 Found
Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA
               &state=xyz

The webview or client app can parse this url parameter to get the authorization code string. If error happens, the url will include "error" parameter and "state" parameter.
example:
HTTP/1.1 302 Found
Location: https://client.example.com/cb?error=access_denied&state=xyz

The authorization code means user grands the client app to access the resource, and so as to use the authorization code to exchange the access token.

3. Once the authorization code is available, the webview opened in step 1 is no longer necessary. the client app can send the following request either in browser or in native library (js or native code). The second request is a POST request to tokenEndpoint, the post body includes
grandType (required)
client_ID (required)
code (required)
redirect url (required if redirect url is included in the initial authorization request)
scope

request sample:
grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
4. Authorization server will return an access token and refresh token (in JSON string format) in the response.

Sample:
     {
       "access_token":"2YotnFZFEjr1zCsicMWpAA",
       "token_type":"example",
       "expires_in":3600,
       "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
       "example_parameter":"example_value"
     }
5. Client app can send the resource request to resource server by presenting the access token into request 's "Authorization" head as a "bearer". 
Header["Authorization"] = "Bearer " + access_token;
example:

   Authorization: Bearer mF_9.B5f-4.1JqM

The resource server must validate the access token and be sure the resource is covered by the scope.


6. In case the access token is expired, it can be refreshed by using refresh token. To do so, sending the request to token access endpoint, with the below parameter
grand_type (required, value must be "refresh_token")
refresh_token (required)
scope (optional)

example:
grant_type=refresh_token&refresh_token=tGzv3JOkF0XG5Qx2TlKWIA

The access token will be returned in the response.

Reference:
https://tools.ietf.org/html/rfc6749

Sunday, November 13, 2016

Swift helper methods for array, dictionary and set

Quite ofter it is needed to loop through each element in an array or dictionary to do certain operation on each element. Swift provides few helper methods to make the operation simple.

The array is used to demo these helper methods

1. sorted, sorted(by:)
Sort an array elements based on default or custom comparison logic. This is a swift standard library function.

Sample sort by default order
        let values = [2, -24, 5, 7]

        let sortedArray = values.sorted()
        print(sortedArray)
            //[-24, 2, 5, 7]

Sample sort by custom comparison

      let sortedArray2 = values.sorted (by: { (a: Int, b: Int) -> Bool in

              return a*a < b*b
            })
        print(sortedArray2)

       //[2, 5, 7, -24]

As the parameter is defined by sorted(by:) function, so the type information can be omitted as 
      let sortedArray3 = values.sorted (by: { a, b in
              return a*a < b*b
            })
        print(sortedArray3)

As the block body only as a single expression, the return clause can also be omitted
 let sortedArray4 = values.sorted (by: { a, b in
              a*a < b*b
            })
        print(sortedArray4)

In addition, the parameter can also referred by their index as $0, $1, $2 from the parameter list, so the expression can be simplified as 
 let sortedArray5 = values.sorted (by: 
              { $0*$0 < $1*$1
            })
        print(sortedArray5)

2. map
For each item in the array, do some operation on it and then return a new array with the processed items.
   let values = [2, -24, 5, 7]
   let square = values.map ({ (item:Int) -> Int in
         return item * item
        })

   print(square)   
   //output
   //[4, 576, 25, 49]

as the function only as a single unnamed block argument, the (), and parameter type information can be simplified as
       let square2 = values.map{ $0 * $0}

        print(square2) 


3. filter
Loop through the collection and return a new collection with the satisfied elements, the returned element type is same as original type

To get the positive even number from the above array
        let filtered = values.filter { (item: Int) -> Bool in
            let i = item % 2
            return i == 0 && item > 0
            }
            
        print((filtered))
        
Simplified express looks like below
        let filtered2 = values.filter {
            let i = $0 % 2
            return i == 0 && $0 > 0
            }
            
        print((filtered2))

4. reduce
reduce can be used to go through each element and accumulate the result from previous element, for example, to calculate the sum
The first parameter is the initial value, which does not need to be the same type as collection element 
        let output = values.reduce("the array is: ", {(ret, item: Int) in
            return ret + (String(item)) })
        print(output)
        //the array is: 2-2457

Simplified express is
        let output2 = values.reduce("the array is: "){$0 + (String($1)) }
        print(output2)

Thursday, November 10, 2016

ios WKWebView security behavior

1. if the main html page is loaded from https connection, then requests using http scheme will be blocked, including xhr request or script element. Even if creating a new iframe, setting the iframe's src to a http url will not load.  However, https content can be loaded into http html page.

2. when sending xhr request to server, the wkwebview didReceiveAuthenticationChallenge delegate method will only be called if the xhr request is sent to the same domain as the html DOM tree. If the xhr request is sent to a different domain, then didReceiveAuthenticationChallenge will not be called, and the 401 https status code will return the js code. This also applies to iframe. didReceiveAuthenticationChallenge delegate method will be called only if the xhr request sent to the same domain as iframe main url.

3. if the main html is loaded from a file url (file://somelocalfilepath), then any xhr requests using file url to the files of the same or sub local folder will fail due to cross domain limit. However, this error can be avoided by setting allowFileAccessFRomFileURLS property in wkwebview configuration as shown below
[theConfiguration.preferences setValue:@YES forKey:@"allowFileAccessFromFileURLs"];  

In addition, xhr request to any remote server will be handled same as cross domain xhr request, which requires the Access-Control-Allow-Origin header to be set, and will also not invoke didReceiveAuthenticationChallenge method.

However, if a iframe is created in the file url html page, then the iframe can be set to a remote url (http or https), and the xhr request inside the iframe on the same iframe domain will work and also can receive the didReceiveAuthenticationChallenge callback.

Monday, November 7, 2016

Understanding iOS WKWebview

Apple recommends to use WKWebview and not use UIWebView starting from iOS 8 as mentioned in https://developer.apple.com/reference/webkit/wkwebview. Developers need to better understand the functions provided by WKWebview.

1. WKWebView

The core object is WKWebView class, the WKWebView has to be created programmatically and can not be added by xcode interface builder. The WKWebView class provider the basic function of loading a url, and go back/forward.

The loadRequest and loadFileURL are two key methods to load the content to the webview.

It also has an important function:
evaluateJavaScript(_:completionHandler:)
for calling an javascript code from native swift/objective-c code.

In addition, wkWebView has a customUserAgent property to easily set the user agent value.

1.1 WKBackForwardList

A list of visited pages.

1.2 WKBackForwardListItem
The url information of the back-forward navigation item

2. WKWebViewConfiguration

When initializing WKWebView instance, one parameter takes a WKWebViewConfiguration object, WKWebViewConfiguration contains a set of properties to config the wkwebview.
WKWebViewConfiguration has few importance members of sub configuration class

2.1 WKPreferences 
minimumFontSize: control miminal font in the browser

javaScriptCanOpenWindowsAutomatically: allow open window without user interaction
javascriptEnabled: whether javascript is enabled. If this property is false, then js script element defined in html file will not be loaded. However WKUserScript will still work. 

Besides the above instance properties, there are few settings can be set to wkwebview's wkpreference by using the setValue method
- (void)setValue:(id)value forKey:(NSString *)key;

For example, call the below method to allow JavaScript running in the context of a file scheme to access content in other file scheme URLs. 
[theConfiguration.preferences setValue:@YES forKey:@"allowFileAccessFromFileURLs"];

The full list of properties are listed at the end of this blog.

2.2 WKProcessPool
The wkwebviewConfiguration can specify a process pool for the webview instance, if multi wkwebview instances share the same wkprocesspool object, then they can share the cookie and cache.

2.3 WKUserContentController
WKUserContentController has two main functions: (1) let native code handle javascript side posted event and (2) inject javascript code from native code when DOM loading starts or ends

2.3.1 Handle js post event from native code
WKUserContentController provides a way for javascript to post js message, and let native code to handle it by the below method:
- (void)addScriptMessageHandler:(id<WKScriptMessageHandler>)scriptMessageHandler name:(NSString *)name;

The native handler needs to implement WKScriptMessageHandler to handle the js event. Note, the method does not provide a way to send the result back to webview, although the handler can call evaluateJavaScript method to report the result to js code. 

2.3.1.1  WKScriptMessageHandler protocol
protocol to receive js message post request with the below method
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message;

2.3.1.2 WKScriptMessage
WKReceiveScriptMessage object represents the message object posted by javascript side sender

2.3.2 Inject javascript from native code to js DOM
WKUserContentController allows add (and remove) a WKUserScript to be injected either when the DOM tree starts to load or finishes to load. On the contrary, evaluateJavaScript allows application to execute a javascript snippet on demand at any time.

2.3.2.1 WKUserScript
WKUserScript represents a js script code block that can be injected into the web page. It includes javascript source code, time to inject the script (DOM start or end), and whether to inject the script only to main frame or all frames


2.4 WKWebSiteDataStore
WKWebSiteDataStore specifies the data store used by a webview, including cookie, caches, and local storage.
The WKWebSiteDataStore class has static method to return the default data store, which is a singleton static instance. WKWebSiteDataStore also has another static method to always return a new nonpersitent data store.

WKWebSiteDataStore provides method (fetchDataRecordsOfTypes and removeDataOfTypes) to enumerate and delete the saved data types (cached, cookie, localstorage, etc) from the website data store.

2.4.1 WKHTTPCookieStore httpCookieStore
A cookie store manages http cookie in the website data store. This object is readonly.
Note when adding cookie change observer, WKWebView configuration will not retain the observer, it must be retained by someone else. In addition, the observer must be added to the WKWebView configuration before setting the configuration's processPool to other value, otherwise the observer will not be called when cookie change happens.

2.4.2 WKWebsiteDataRecord
WKWebsiteDataRecord represents website data grouped by url's domain and suffix

2.5 WKURLSchemeHandler (iOS 11+)
WKWebViewConfiguration can set custom url handler to customize request handling by calling the below method, similar to NSURLProtocol. 
However this only works if the root html file is also loaded using the same custom scheme. If the root html file is loaded by file:// or http:// (or https://), then setting the registered custom scheme handler will not work.
- (void)setURLSchemeHandler:(id<WKURLSchemeHandler>)urlSchemeHandler forURLScheme:(NSString *)urlScheme; 

3 WKNavigationDelegate

WKNavigationDelegate methods are called when loading a navigation request, for example, event to begin the load and finish the load. It also has a method to handle server side credential challenge. It uses WKNavigation object to indicate the navigation information. The delegate can decide to allow or forbid a pending navigation

3.1 WKNavigation
WKNavigation is a unique object to identify the loading progress of a page, it does not have any property or method.

3.2 WKNavigationAction
WKNavigationAction class is used by method decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction as a parameter to indicate the navigation information, so caller can decide whether allow the navigation or not  based on its information. It includes (NSURLRequest*) request, navigationType, etc.

Note if the decidePolicyForNavigationAction  method returns cancel, then the navigation error callback method will not be called. Those navigation error callback methods are only called when the page cannot be loaded for other reasons.

3.2.1 WKNavigationType
Used by WKNavigationAction to indicate the navigation type, like a link, or form submit, or back-forward request.

4 WKUIDelegate

Unlike mobile safari, WKWebView does not  handle javascript alert, confirm and prompt method by default, you need to implement WKUIDelegate to support them. 
When javascript calls the alert, confirm or prompt method, the native method in WKUIDelegate will be called, you need to show a native UIAlert dialog UI to user, once user dismiss the dialog, then calling the completion block to tell the javascript code to continue.
When the below methods are invoked from js side, the js flow is blocked, so they are synchronous methods. A particular usage for confirm and prompt method is to call the confirm or prompt method from js code and then pass some data from native code to javascript with or without UI interaction.


- (void)webView:(WKWebView *)webView 
runJavaScriptAlertPanelWithMessage:(NSString *)message 
initiatedByFrame:(WKFrameInfo *)frame 
completionHandler:(void (^)(void))completionHandler;
- (void)webView:(WKWebView *)webView 
runJavaScriptConfirmPanelWithMessage:(NSString *)message 
initiatedByFrame:(WKFrameInfo *)frame  
- (void)webView:(WKWebView *)webView 
runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt 
    defaultText:(NSString *)defaultText 
initiatedByFrame:(WKFrameInfo *)frame  


Appendix: List of properties in WKPreference


telephoneNumberDetectionIsEnabled
storageBlockingPolicy
compositingBordersVisible
compositingRepaintCountersVisible
tiledScrollingIndicatorVisible
resourceUsageOverlayVisible
visibleDebugOverlayRegions
simpleLineLayoutEnabled
simpleLineLayoutDebugBordersEnabled
acceleratedDrawingEnabled
displayListDrawingEnabled
visualViewportEnabled
largeImageAsyncDecodingEnabled
animatedImageAsyncDecodingEnabled
textAutosizingEnabled
subpixelAntialiasedLayerTextEnabled
developerExtrasEnabled
logsPageMessagesToSystemConsoleEnabled
hiddenPageDOMTimerThrottlingEnabled
hiddenPageDOMTimerThrottlingAutoIncreases
pageVisibilityBasedProcessSuppressionEnabled
allowFileAccessFromFileURLs
javaScriptRuntimeFlags
standalone
diagnosticLoggingEnabled
defaultFontSize
defaultFixedPitchFontSize
fixedPitchFontFamily
offlineApplicationCacheIsEnabled
fullScreenEnabled
shouldSuppressKeyboardInputDuringProvisionalNavigation
allowsPictureInPictureMediaPlayback
applePayCapabilityDisclosureAllowed
loadsImagesAutomatically
peerConnectionEnabled
mediaDevicesEnabled
screenCaptureEnabled
mockCaptureDevicesEnabled
mockCaptureDevicesPromptEnabled
mediaCaptureRequiresSecureConnection
enumeratingAllNetworkInterfacesEnabled
iceCandidateFilteringEnabled
webRTCLegacyAPIEnabled
inactiveMediaCaptureSteamRepromptIntervalInMinutes
javaScriptCanAccessClipboard
domPasteAllowed
shouldAllowUserInstalledFonts
editableLinkBehavior

Note, an important property AllowFileAccessFromFileURLs is not supported by ios, although it is supported by Android client.

Thursday, November 3, 2016

How to programmatically get team id after build ipa file

The easiest way to get team id from ios IPA file is set the following key-value pair in info.plist file

<key>AppIdentifierPrefix</key>
<string>$(AppIdentifierPrefix)</string>

After building the xcode project, open the app bundle ipa package, the generated info.plist file will replace the placeholder of $(AppIdentifierPrefix) with the real team id used to sign the ipa file. You can also programmatically read the value based on the key of AppIdentifierPrefix at runtime.

However, this only works if the ipa will not be resigned with a different  entitlement file. If the ipa file is resigned with a different certificate and entitlement file, the easy way to find the team id is first writing an item in keychain and then read the item's access group back, it will include the team id information in the access group attribute.