Back-End Connectivity¶
Much of the Foundation APIs' functionality is closely tied to the network communication to the back-end servers. While
Foundation APIs make it easier for developers to write applications that interact with servers, the APIs rely on the
okHttp
library for network communication. In order to give developers using the foundation library more control, the
classes that make networking calls use the OkHttpClient
that is directly given to them, or fall back to use the
OkHttpClient
that is set globally on ClientProvider
.
Authentication¶
When a mobile application is configured with an authentication method, any access to the back end that uses REST API requires an authenticated session context or valid authentication headers for authentication. Authentication modules provided with the SDK help you with authentication.
Typical usage of OkHttpClient
involves setting up an instance with the necessary authenticator or interceptor and
setting that instance globally on ClientProvider
for any method calls that interact with mobile services.
...
...
OkHttpClient mobileServiceNetworkingClient = new OkHttpClient.Builder()
.addInterceptor(new SamlInterceptor(samlConfiguration))
.cookieJar(new WebkitCookieJar())
.build();
ClientProvider.set(mobileServiceNetworkingClient);
...
Settings mobileAppSettings = new Settings();
mobileAppSettings.load();
...
...
RemoteNotificationClient mobileServicePushClient = new RemoteNotificationClient();
Foreground Activity Tracking¶
When establishing a new session, authentication modules may need to interact with the user to obtain credential
information. To accomplish this, the modules need to know which activity is in the foreground. The Foundation component
has a AppLifeCycleCallbackHandler
singleton that is used to find which activity is in the foreground.
This callback handler must be registered using android.app.Application.registerActivityLifecycleCallbacks
before any
networking calls are made. Once registered, the ApplLifeCycleCallbackHandler
will be able to keep track of the
foreground activity.
See the Authentication topic for more information.
Mobile Service HTTP Headers¶
Some functionality in mobile services depend on special HTTP headers. The presence of these headers in a request affects certain functions like logout.
Header | Description |
---|---|
X-SMP-APPID | The application id of the mobile app as configured in SAP Mobile Services |
X-SMP-DEVICEID | Device Id |
X-SMP-APP-VERSION | The mobile application version |
X-SMP-SDK-VERSION | The version of the SDK the app is using |
AppHeadersInterceptor
¶
The foundation library provides AppHeadersInterceptor
that automatically adds these headers to the OKHttp
request if
they are not already present. In addition to the headers specifically for Mobile Service, AppHeadersInterceptor
also
adds the Accept-Language header to requests that do not already have one. The Accept-Language header added by
AppHeadersInterceptor
is of the form "LA-CO, LA;q=0.9" where "LA" is the language code of the device's locale, and
"CO" is the country code of the device's locale.
To use this class, add an AppHeadersInterceptor
when building an OKHttpClient
. The following example shows how to use
AppHeadersInterceptor
in a no-auth
configuration. Please note that usually this interceptor is added in addition to
the other authentication interceptors.
// Construct AppHeadersInterceptor without input parameter, SettingsParameter object is retrieved from SettingsProvider class.
AppHeadersInterceptor appHeadersInterceptor = AppHeadersInterceptor();
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(appHeadersInterceptor)
.build();
Cross-Site Request Forgery Protection¶
If you want to use Cross-Site Request Forgery (CSRF) protection for your application, first you must enable this feature in the SAP mobile service cockpit. See: Defining Application Security for more information.
If CSRF protection is enabled, you need to send an X-CSRF-Token
header for the modifying HTTP requests. The
CsrfTokenInterceptor
can automatically handle this for every request.
Note
The CSRF Protection option protects all services, such as registration, with CSRF tokens. Proxied endpoints are not protected, since they may be protected on the back end.
Basic Usage¶
The following example shows how to add the CsrfTokenInterceptor
if you have one root URL for every CSRF token request.
The rootUrl
parameter is the URL from which the CSRF token will be requested.
String rootUrl = "https://myserver.hana.ondemand.com/odata/applications/v4/myappid/";
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new CsrfTokenInterceptor(rootUrl))
.build()
Advanced Usage¶
If you have multiple CSRF protected back ends and these back ends accept different CSRF tokens, you must implement a
CsrfTokenUrlProvider
to provide CSRF URL for different request URLs. For token storing, you should implement a
CsrfTokenStore
. A default implementation of the store is provided that will keep a token per host and port.
public class MyCsrfTokenUrlProvider implements CsrfTokenUrlProvider {
@Override
public String getTokenUrl(String url) {
// TODO: implement custom logic to create token URL from the request URL
return ...
}
}
public class MyCsrfTokenStore implements CsrfTokenStore {
@Override
public void setToken(String token, String url) {
// TODO: implement
}
@Override
public String getToken(String url) {
// TODO: implement
return ...
}
@Override
public void deleteToken(String url) {
// TODO: implement
}
}
For more information about how this protection works, see the following topics:
User Blocking¶
An administrator can block a particular user by setting a condition at the server. A blocked user is unable to register
an application, receive data, or both. When a user is blocked, all HTTP requests from that user’s applications receive a
403 (Forbidden) status response with an X-MESSAGE-CODE
header. The header value indicates whether registration,
traffic, or both is blocked. At this point, the application can no longer communicate with the server and should notify users that they are blocked.
The foundation library provides the BlockedUserInterceptor
class to facilitate handling this condition. It is
constructed with a callback function and added to the interceptor chain of the OkHttpClient
. The specified callback is
invoked on the first receipt of a 403 response with the X-MESSAGE-CODE
header from the server. Subsequent reception of
the blocked response will not trigger the callback again until a successful response is received or the application is
restarted.
The callback will be supplied with an enum
value representing the blocked type that was in the response header.
X-MESSAGE-CODE Value Summary¶
enum Value | Header Value | Description |
---|---|---|
UNDEFINED_BLOCK | N/A | An unrecognized value was received |
REGISTRATION_BLOCKED | REG_BLOCKED | All application registrations by this user are blocked |
TRAFFIC_BLOCKED | TRAFFIC_BLOCKED | All data for this user is blocked |
TRAFFIC_AND_REGISTRATION_BLOCKED | TRAFFIC_REG_BLOCKED | Both registration and data for this user is blocked |
REGISTRATION_WIPED | REGISTRATION_WIPED | This user's registration at the server has been wiped |
Please note that the callback is invoked synchronously. Therefore, it needs to return in a timely, non-blocking manner. The developer is free to start a thread or an activity in the callback if that is necessary.
To use this class, add a BlockedUserInterceptor
to the OkHttpClient
instance. Please note that usually this
interceptor is added in addition to the other interceptors.
// Construct BlockedUserInterceptor supplying a callback function.
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new BlockedUserInterceptor( new BlockedUserInterceptor.BlockedUserCallback(){
@Override
public void handleBlockedUser(BlockedUserInterceptor.BlockType blockType) {
// TODO: inform the user that they are blocked by the administrator
}
} ))
.build();
Lock and Wipe¶
Lock and Wipe is a policy in the setting data provided by the server. This policy specifies the number of days that are allowed to elapse without authenticating with the server before the application is locked or its data is wiped. The settings data from the server is returned by the Settings object in the foundation library.
The Lock and Wipe Policy is set for the application at the server. This is labeled "blockWipingPolicy" in the JSON that is sent down to the client. Lock is named "blockDisconnectedPeriod" and wipe is "wipeDisconnectedPeriod". Both values are in days.
If "blockWipeEnabled" is true and either or both of these are greater than zero, then the corresponding action is taken if the application fails to connect to the server within the specified number of days.
Presumably, the wipe period is longer than the lock period. If the lock period is exceeded, then the application should be locked until the user successfully connects to the server.
If the wipe period is exceeded, then the application data should be reset.
A LockWipePolicy
class is provided by the foundation library. The "blockWipingPolicy" is parsed by the
Settings object. The values in
mobileservices/settingsExchange/blockWipingPolicy
are used to construct a LockWipePolicy
object. These previously
described values are:
- "wipeDisconnectedPeriod"
- "blockWipeEnabled"
- "blockDisconnectedPeriod"
Two callback methods can also be passed to the LockWipePolicy
constructor in addition to these values. These are
lockCallback
and wipeCallback
which handle the lock and wipe conditions respectively. Alternatively, the
LockWipePolicy
can be constructed without the callbacks and they can be set at a later time by using setLockCallback
and setWipeCallback
.
// Lock and Wipe policy values parsed and extracted through the Setting object JSON
JSONObject settingsJson; // result from Settings.CallbackListener.onSuccess()
boolean enabled = false;
int lockDays = 0;
int wipeDays = 0;
JSONObject lockWipePolicyJson = settingsJson.optJSONObject("blockWipingPolicy");
if (lockWipePolicyJson != null) {
enabled = lockWipePolicyJson.optBoolean("blockWipeEnabled", false);
lockDays = lockWipePolicyJson.optInt("blockDisconnectedPeriod", 0);
wipeDays = lockWipePolicyJson.optInt("wipeDisconnectedPeriod", 0);
}
// Construct the Lock Wipe Policy supplying two callback functions.
LockWipePolicy lockWipePolicy = new LockWipePolicy(
enabled,
lockDays,
wipeDays,
new LockWipePolicy.LockCallback() {
@Override
public void handleLock() {
// TODO: Inform the user that the application is locked due to not connecting with the server
}
},
new LockWipePolicy.WipeCallback() {
@Override
public void handleWipe() {
// TODO: Reset the application data
}
}
);
A call to LockWipePolicy.apply()
must be made after the client policy is retrieved through the Settings
object and
the LockWipePolicy
object is constructed and the callbacks assigned. The respective callback will be invoked in the
case where the lock or wipe intervals have been exceeded. Wipe is evaluated first. If it is zero or within range then
lock is evaluated. In this way, only one of the callbacks can be called for a single invocation of
LockWipePolicy.apply()
.
@NonNull
Date retrieveLastConnectionTime() {
Date lastConnectionTime;
// TODO: Retrieve the persisted the last connection time
return lastConnectionTime;
}
lockWipePolicy.apply(retrieveLastConnectionTime());
The call to LockWipePolicy.apply()
requires the last connection time. This corresponds to the last time that the
application communicated with the server. To accommodate this, the application must save the current time (GMT) in the
application secure store each time it communicates with the server. This is facilitated by the
LastConnectionTimeInterceptor
class provided by the foundation library. It is constructed with a callback function and
added to the interceptor chain of the OkHttpClient
. The specified callback is invoked every time a success (200)
response is received from the server.
// Construct LastConnectionTimeInterceptor supplying a callback function.
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new LastConnectionTimeInterceptor( new LastConnectionTimeInterceptor.LastConnectionTimeCallback(){
@Override
public void updateLastConnectionTime() {
Date lastConnectionTime = new Date();
// TODO: Persist the last connection time
}
} ))
.build();