1 2 3 23 Previous Next

SMP Developer Center

338 Posts



Identity integration is just as important as Data integration.


I am working on a longer series of articles describing end-to-end HCP mobile application development, but I wanted to take the opportunity to post a quick solution to a problem I have been researching for some time: How can you effectively capture and incorporate a user's identity in your Olingo OData web services?

 

Integrated Identity Management services are a cornerstone of the HANA Cloud Platform.  These services can take many forms.  You can use any SAML 2.0 Identity Provider (IdP) as your user base. The IdP can be SAP's Cloud Identity Tennant, a commercial Cloud identity provider such as Microsoft Azure.  It can also integrate with your own on-premise Identity Provider such as AD FS or Shibboleth.  HCP's IdP Trust configuration is fairly straightforward to set up if you are familiar with SAML and an integration can include passing custom Group membership information along with other user attributes into all HCP applications.

 

In this article, I will give you a quick glimpse into how you can access identity information in an Olingo web service that you might deploy in HCP.

HCP Identity.png

Apache Olingo is a handy open source framework for developing RESTful OData web services. Olingo supports several modes for defining the data model of a web service. I find the JPA mode easy to work with -- Olingo's JPA API allows your choice of either defining a web service object model from scratch by defining your own JPA Java objects or by using Eclipse to automatically generate a JPA model from a set of source SQL tables.

 

Defining a data model, though, only addresses a portion of the practical challenge of creating a new web service.  Securing the web service is an essential element too, and that often requires integrating user identity tightly into the security implementation.

 

For example, let's say I wanted to create a web service supporting a messaging application. In order to secure the messages from prying eyes, I might leverage the user's identity to filter the messages down to only those messages the logged-in user is authorized to view. Commonly used OData training samples, such as ESPM or Northwind conveniently overlook such scenarios.

 

It turns out that it is relatively easy to add such intrinsic "relevance" filtering to an Olingo entity collection.  Let's look at how it works, but let's first elaborate a bit on our use case so that what we construct will make the most sense.

 

Here's a simple set of rules describing the elements and behavior of our application:

 

  1. A User can send a Message to one or more other Users.
  2. Messages are grouped into Conversations.
  3. Each Conversation has a list of Users that are members.
  4. A User may be a member of any number of Conversations.
  5. Only members of a Conversation are allowed to view that Conversation's Messages.
  6. A User can only view the Conversations that they are a member of.
  7. When A Message is created, the Message sender is assigned automatically by the web service. (In other words, a User cannot "send" a Message impersonating another User.)
  8. Users may create new Conversations on demand.
  9. A User may add an additional user to any Conversation they belong to.

 

This list of rules isn't exhaustive by any stretch, but it defines how we would like this web service to behave well enough that what follows should make sense.

 

Items one through four can be expressed by this entity relationship diagram:

 

HCP Messaging.png

Starting from this ERD, it is easy to use Olingo's JPA API to define Java objects that will manifest this structure in a persistent SQL database and simultaneously create corresponding OData Entity Collections.

 

Items five, six, and seven all express a need for the web service to use information about the logged-in user to either filter the data to what's authorized or to ensure information integrity.


So, how do we efficiently obtain user information in an Olingo JPA web service running in HCP?


The first step is to obtain the name of the authenticated user.  Olingo's JPA code saves the HttpServletRequest object corresponding to each inbound request in a way that fairly easy to retrieve:

 

import org.apache.olingo.odata2.jpa.processor.core.ODataJPAContextImpl;
import org.apache.olingo.odata2.api.processor.ODataContext;
import javax.servlet.http.HttpServletRequest;
    .
    .
    .
  ODataContext ctx = ODataJPAContextImpl.getContextInThreadLocal();
  HttpServletRequest r = (HttpServletRequest) ctx.getParameter(ODataContext.HTTP_SERVLET_REQUEST_OBJECT);
  String user = r.getRemoteUser();
  if (user != null) {
      MessageUser u = IdentityInteraction.verifyUser(r, em);
      logger.debug( "username from HttpServletRequest '"+u.getUsername()+"'" );
  }
  else {
      logger.error("Assertion error: There is no authenticated user defined in the HttpServletRequest -- " +
          "check your web.xml application configuration");
  }










That will return the username, but we'd like to be able to access other metadata about that user too -- what about their first name or last name? -- or their e-mail address?  HCP provides some helper classes to access those user attributes.


import com.sap.security.um.user.UnsupportedUserAttributeException;
import com.sap.security.um.user.User;
import com.sap.security.um.user.UserProvider;
import javax.naming.InitialContext;
.
.
.
 InitialContext ctx;
  try {
      ctx = new InitialContext();
      UserProvider userProvider;
      userProvider = (UserProvider) ctx.lookup("java:comp/env/user/Provider");
      User user = null;
      if (request.getUserPrincipal() != null) {
          nameId = request.getUserPrincipal().getName();
          user = userProvider.getUser(nameId);
          if ( user != null) {
              try {
                  email = user.getAttribute("email");
              } catch (UnsupportedUserAttributeException e) {
                 // no error
              }
              try {
                  lastName = user.getAttribute("lastname");
              } catch (UnsupportedUserAttributeException e) {
                  // no error
              }
  try {
    firstName = user.getAttribute("firstname");
   } catch (UnsupportedUserAttributeException e) {
  // no error
    }
    }
  }
    } catch (NamingException e1) {
    logger.error("NamingException insde IntentityInteraction housekeeping: " + e1.getMessage());
    e1.printStackTrace();
    } catch (PersistenceException e2) {
  logger.error("PersistenceException insde IntentityInteraction housekeeping: " + e2.getMessage());
  e2.printStackTrace();
   }






 

In order to take advantage of this lookup, your application's web.xml file must include this resource reference in the web-app definition:

 

  <resource-ref>
    <res-ref-name>user/Provider</res-ref-name>
    <res-type>com.sap.security.um.user.UserProvider</res-type>
  </resource-ref>






Summary


The Java code shown here can be used to look up user information in any Olingo JPA web service running in HCP.  If you are running in a different container, it will likely have its own way to lookup user attributes.


I am working on a complete working sample based on these code snippets.  Until that's available, hopefully this will help anyone trying to solve this same problem in their own Olingo projects. In a following article, I'll show you exactly where to insert this code into your Olingo JPA web service and how to add extra relevance and security checks into each OData Entity Collection.

Hi there.

It's time to talk about new features, since as of today we have released version 1.2 of HCPms.

 

Sometimes it's not easy to weight the importance of a feature and give it the right visibility, and for this release I was really struggling on deciding which feature I would like to present first. I have decided for a developer centric one. A feature that might look simple, or obvious but in the end it greatly eases the life of developers. The feature I have in mind is SCIM support for HTTP Basic authentication.

 

SCIM Support (HTTP Basic Auth)

You may wonder about this, because you thought that BASIC auth was possible before this release and you where right. You can configure your App in the way that it accepts BASIC auth headers (or challenge you once you request a resource) against SAP ID service - which is you default IdP.

With SCIM (SCIM webpage) support you can expose the HTTP challenge that is configured directly on you OData Service. This enables you to expose the users that are configured on your services URL to be authenticated on the mobile device.

Actually there are two different version of SCIM available now. One is provided by HCP itself and the other is a mobile services specific SCIM.

The SCIM provided by HCP (and Cloud Connector) has a different scope, since it is valid for all App on you account - so XS or JAVA applications are affected as well, while the mobile specific SCIM service is only valid within mobile services and can be configured for each application differently.

 

So basically, if you would like to expose an on-Premise LDAP to your HCP account - you would use the HCP SCIM. If you want to reuse your users on a specific OData Service, then you would choose the mobile service specific SCIM.

This is (unfortunately) not available for trial landscape.

 

Preview Landscape

Many customers expresses their concerns about the loss of control of the lifecycle of their system - which is in this case HCPms. SAP controls the lifecycle, installing patches, releases, changing API and so forth. While this is one of the beauties of an as-a-Service offering, we take those thoughts serious. And with this release we offer each customer access to a preview landscape. This landscape can be requested via a self-service and is intended to be used for regression testing. The preview landscape will receive the newest release on RTC date (Release to Customers). Currently we plan to provide a minimum of thirty days preview period before we deploy the new version to the productive landscape and update all our customer landscape. During this preview period we encourage our customer to use the SAP Cloud Support to notify us about any incompatibility they found during their tests.

 

Mobile Web App Support

No, this does not mean you can host web applications in mobile services. There are other services on HCP that do this. Support for mobile web applications does mean that we now support a new type of App configurations, called "Web". This allows you to proxy existing web hosted applications - either on the web or through the Cloud Connector for on-premise web apps (like a specific portal page).

The Web app does two things for you. First it exposes an on-premise web app and makes it available to your mobile browser on the device without the need to start a VPN connection on the device or using a special application like the SAP Fiori Client. Second, it can add authentication in front of your webpage as it would challenge your mobile browser with a login page that uses the authentication that is configured on your App - SAP ID service for example.

 

And this does work for on-premise Fiori applications as well!!

 

Administration User Experience Improvements

 

We did a log of small changes in the UI for the Admin Cockpit to improve the user experience for the users of HCPms - which are developers and administrator. It's no fun to list all the small changes and tweaks we did. I would rather suggest that you take a look yourself. One thing I want to disclose here, though.

If you visit the Admin Cockpit the first time you'll see this:

 

Whatsnew.png

GLAS Auditing

This is probably not the thing your are waiting for, but it's essential for license compliance anyway. We do support the standard SAP Global Licensing Auditing Service. This allows our customers to keep track of their used licenses easily and report them back to SAP. Well, as I said, not a thing a developer wants to bother with.

 

 

Release Notification Information

We have published the following document to inform you more formally about changes in HCP mobile services. Please use the subscription feature of SCN in order to be informed about Product changes in the future.

SAP HANA Cloud Platform mobile services - Release Notification

 

 

That's it for now. Stay tuned and

 

 

Have Fun,

Martin

When downgrading from Mobile SDK SP09 to SP08 to check some partner's code, I encountered the following error message: 

 

"The Vital Product Data registry is in use by another installer.  Please start this installer after the other installation is finished."

Screen Shot 2015-07-21 at 11.12.46 AM.png

Resolution

Check if this file exists:  "~/Installshield/Universal/SMPClient/instance.running", remove it, and try installer again.

We are pleased to announce the release of SDK SP09 to service market place. The major focus for this release was to provide enhancements, improve stability and lay the frame work for new features.

 

The new or changed features for the different components of the SDK are the following.

 

Native SDK

 

The application settings in the initial versions of  LogonCore component could be accessed from ConnectionData property for iOS and Windows. For the Android, only some settings were available through dedicated properties and the ConnectionData property did not exist. In SP08, the ApplicationSettings property of the LogonCore instance replaced the ConnectionData property on the RegistrationContext for accessing server application settings.

In SP09, the ConnectionData property has been enhanced to support the V2 registration service provided in SAP Mobile Platform Server version 3.0 SP08.

 

We have also updated the usage guides for supportability with detailed examples of how to implement logging and end-to-end tracing for all supported platforms.

 

Hybrid SDK


The following are some of the enhancements and updates to the hybrid SDK.

 

  • Support for Apache Cordova CLI 5.1.1 for Hybrid SDK (Kapsel) applications.
  • The Push plugin has been enhanced to support management of push notifications through SAP Push Hub.
  • The Feature Restriction policy is now supported for use with Windows applications, in addition to Android and iOS.
  • The X.509 certificate provider interface has been updated to reflect changes to the interfaces, new sample provider projects, support for authentication without SAP Mobile Platform, and support for a Windows interface.
  • Changes to networking support affect Windows applications that use the SAP.Net.Http networking component

 

SAP Fiori client packaging*


We are also introducing a new feature to support the SAP Fiori client packaging . Customers who have already installed a set of Fiori Apps (for example, CRM) can make these applications available in a packaged form suitable for installing on a mobile device and built using the Cordova and Hybrid SDK (Kapsel). The CLI packager tool  is a node.js script bundled with the Hybrid SDK and it allows users to create a prepackaged Fiori application by obtaining assets from a SAP Fiori Front-End Server (FES).


*For the packager to work , the SAP cloud build service from Mobile Secure is needed as a prerequisite. This is currently not generally available at the time of writing this blog.

 

Agentry Toolkit

 

The following are the updates to the Agentry toolkit


  • Agentry applications now fully support the Secure Sockets Layer (SSL) standard, including Alternate names in SSL certificates ,Wildcards in common names in SSL Certificates,Download of certificate chain
  • Agentry editor changes to support properties like screen set header style , list tile view row style
  • Documentation updates for Open UI and Data API

Hello Coders,

 

     On a fine morning few weeks back when my manager called me up and said  'Hey Vishnu ,how about implementing SMP 3.0 in our network??'.Do you want to know what i was thinking ?  'Hell yeah!!!! this is what we call an opportunity'.

 

There were certain things that pulled me back.

  • No Consultants in my organisation to guide me
  • No previous experience in setting up a server and configuring it to make it work
  • And the fact that i was all alone for this task

 

 

Ok,let's say i didn't bother about the above mentioned points,because i have already set my mind to go for it.

 

Gods grace server set-up was done and i would say 'perfecto' and there weren't any issues.

 

I started of by following a document of Jitendra Kansal  SMP 3.0: An End to End guide to create an OData service for a given SAP Gateway data source

 

Even though the tutorial was using a SAP Delivered service,for some reason i decided to use the service that i created.This decision drove me to dig more into SMP IGW and made me hunt for answers all over google,scn and wherever i could.

 

After following every steps my output was so disappointing.

 

smp_systemadmin.JPG

 

 

I didn't understand a single word what my Browser was trying to tell me.

 

There were some interesting stuff i noticed while i was registering my service.Even though my service was under a NAMESPACE it wasn't shown in the Gateway Cockpit during registration.

 

 

service_loc.JPG

 

 

search_service.JPG

 

 

 

After getting inputs from Pavel Penaz i started troubleshooting process in IGW.

 

pavel1.PNG

 

 

pavel2.PNG

 

 

 

After performing the above steps i could retrieve some error in the Gateway Cockpit.

 

error_log_migo1.JPG

 

 

'(Function call failed; could not find the function Z_MFW_MIGO_UI5_PO_LIST)'.



I Dint understand why it was searching for the FM in HUB System,but before digging more into that let my explain how the systems were setup in my corporate network.


1)Front Server (GW_CORE,IWBEP,IWFND)

2)Backend ECC 6.0

 

i.e we have a HUB Deployment.

 

  • I have created an odata service in Frontend where it internally calls the an RFC Function in Backend.

 

 

At this point Bjoern Woppmann was looped into the discussion.And gave as a exhaustive explanation as below.

 

bjoer.JPG

 

 

 

And After reading Capabilities Matrix: Which SAP product to use to expose your business data as OData service for UI consumption

most the confusions i had were gone ...  

 

 

 

All the above were based on the discussion Error while calling the entity set in SMP 3.0 SP07(via IGW) that i had in this community.I tried to consolidate it into a single blog so that anybody who is trying to step on to SMP 3.0 would feel helpfull(i guess so).

 

For more on Consuming service with IGW , it would be nice for every OData Developer to have an insight on Custom Coding.Carlos Roggan have a great Blog for every Beginners. Integration Gateway: REST data source, overview of Blogs

 

Happy Coding

Cheers,

Vishnu

As Sami announced in his blog SAP Mobile Platform 3.0 (SP08) had been released for download in SAP service marketplace since June, I will just take this opportunity to recap what are new in SP8 for integration gateway.

 

1. Unified Admin UI

We have been listening to you! One of the feedback on integration gateway was the separated admin cockpit from SMP admin page. Which are confusing for end users who need to switch back and forth the admin URLs for SMP and integration gateway. In SP8, we now have a unified admin UI. A new tab called OData Services is added to SMP Admin page. This is the same integration gateway admin page where you can configure OData services, manage backend destinations and monitoring logging. Except that you don't need to do that in a separated URL anymore. Good stuff!

Unified UI.jpg


2. Deep Insert for REST

REST is one of the most popular data sources we see in the market. We released full CRUD support for REST in SP7. With SP8 we now have support of deep insert for REST. That means you can create related entities in one request. For example, you can create a sales order and a sales order item together and at the same time, and for REST, not only for JDBC data source that you had seen in SP7.

 

3. Delta Token on Complex OData Model

Delta handling is key for improving mobile app performance. Delta token handling was available with custom script (Java Script or Groovy) in SP5. With SP8 we are expanding the delta handling to complex OData model with parent-child relationship. Please note at this release the delta handling is still restricted to parent level entity but not child level entity. And as a side note, in TechEd 2015 Las Vegas, I will be hosting a 2 hrs hands-on session with SAP RIG colleagues on delta handling for mobile app development. Please stop by if you want to learn more on integration gateway and delta handling. We are looking forward to seeing you there.

 

Those are some main features for Integration Gateway in SMP 3.0 SP8. We are working on some pretty cool features for the upcoming SP9. So again, stay tuned... and thanks for your attention!

 

Related blogs:

Hi Folks,

 

Before proceeding on this blog, i would like to thank Claudia Pacheco for her wonderful self explanatory blog How to consume OData Services in online Mode (Android) which really helps me in achieving my goal.

 

 

As i was proceeding on my app development, i faced a requirement to consume multiple OData services from a single data source. As a newbie on SMP and Android side i faced few challenges in starting to achieve it and tried to find some useful code but hard luck. Later on i got the success. So, thought to share how i implemented hoping it may become useful to other newbie like me.

 

Prerequisites

 

  • SMP SDK 3.5+ & Android Studio installed on machine
  • Added the MAF resources and completed the on boarding of users on SMP 3.0 using MAF logon UI /Core libraries
  • Have set the primary back-end point url and created the multiple back-end connections for multiple OData.

 

Here i'll take an example of read and create operation with two different OData services.

 

Steps to Achieve

 

Step 1 : Creating the MultipleOpenListner class which implements the OpenListner as "multipleOpenListner.java"

 

public class multipleOpenListner implements OpenListner{
//creating multiple listener instance for multiple back-end connections
public static leOpenListner Instance = null, readInstance, createInstance ;
private final CountDownLatch latch = new CountDownLatch(1);
OnlineODataStore store;
Exception error;
public static multipleOpenListner getInstance(String ch){
switch(ch){
case "read":if(readInstance == null){
readInstance = new multipleOpenListner();
Instance = readInstance;
}
break;
case "create":if(createInstance == null){
createInstance = new multipleOpenListner();
Instance = createInstance;
}
break;
  }
return Instance;
}
@Override:
public void storeOpened(OnlineODataStore store){
this.store = store;
latch.countdown();
}
@Override:
public void storeOpenError(ODataException e){
this.error = e;
latch.countDown();
}
public synchronized boolean finished(){
return (store != null || error!=null);
}
public synchronized Exception getError(){
return error;
}
public synchronized OnlineODataStore getStore(){
return store;
}
public void waitForCompletion(){
try{
if(!latch.await(30, TimeUnit.Seconds)){
throw new IllegalStateException("Open listener was not called within 30 seconds");
}
else if(!finished()){
throw new IllegalStateException("Open listener is not in finished state after having completed successfully");
}
}
catch(InterruptedException e){
throw new IllegalStateException("Open Listener waiting for result was interrupted");
}
}
}



 

Step 2 : Opening OnlineODataStore under the class "OnlineManager.java"

 

public class OnlineManager {
   public static final String TAG = OnlineManager.class.getSimpleName();
   public static boolean openOnlineStore(Context context, String conCase) throws ODataException{   multipleOpenListner openListener = multipleOpenListner.getInstance(conCase);
   if (openListener.getStore()==null) {
  LogonCoreContext lgCtx = LogonCore.getInstance().getLogonContext();
  IManagerConfigurator configurator = LogonUIFacade.getInstance().getLogonConfigurator(context);
  HttpConversationManager manager = new HttpConversationManager(context);
  configurator.configure(manager);
   //XCSRFTokenRequestFilter implements IRequestFilter  //Request filter that is allowed to preprocess the request before sending   XCSRFTokenRequestFilter requestFilter =
  XCSRFTokenRequestFilter.getInstance(lgCtx);
  XCSRFTokenResponseFilter responseFilter =
  XCSRFTokenResponseFilter.getInstance(context, requestFilter);
  manager.addFilter(requestFilter);
  manager.addFilter(responseFilter);
   try {
  String endPointURL ="";
   switch(conCase){
   case "read": endPointURL = lgCtx.getAppEndPointUrl();
   break;
   case "create": endPointURL = "http://server:8080/backEnd_ConnectionName";
   break;
   default : break;
  }
  URL url = new URL(endPointURL);
   // Method to open a new online store asynchronously   OnlineODataStore.open(context, url, manager, openListener, null);
  openListener.waitForCompletion();
   if (openListener.getError() != null) {
   throw openListener.getError();
  }
  } catch (Exception e) {
   // throw e;   Log.e(TAG, e.getLocalizedMessage(), e);
  }
   //Check if OnlineODataStore opened successfully   OnlineODataStore store = openListener.getStore();
   if (store != null) {
   return true;
  } else {
   return false;
  }
  } else {
   return true;
  }
   //End   }
// Method to read
public static ArrayList read(String conCase) throws ODataException{
  ArrayList<String> arrList = new ArrayList<String>();
  multipleOpenListner openListener = multipleOpenListner.getInstance(conCase);
  OnlineODataStore store = openListener.getStore();
   if (store!=null){
     ODataProperty property;
  ODataPropMap properties;
   try {
   //Executor method for reading an Entity set synchronously   ODataResponseSingle resp = store.executeReadEntitySet("Entity",null);
   //Get the response payload   ODataEntitySet feed = (ODataEntitySet) resp.getPayload();
   //Get the list of ODataEntity   List<ODataEntity> entities = feed.getEntities();
   //Loop to retrieve the information from the response   for (ODataEntity entity: entities){
  properties = entity.getProperties();
  property = properties.get("Property_Name");
  String propName = property.getValue().toString();
  arrList.add(propName);
  }
  } catch (Exception e) {
  Log.e(TAG, e.getLocalizedMessage(), e);
  }
  }
   return arrList;
}
// Method to create
public static void create(UIListener uiListener, String conCase) throws ODataException{   multipleOpenListner openListener = multipleOpenListner.getInstance(conCase);
  OnlineODataStore store = openListener.getStore();
   if(store==null) return;
   try{
  ODataEntity newEntity = createEntity(store);
  RequestListener requestListener = new RequestListener( Operation.Create.getValue(),uiListener);
   //Scheduling method for creating an Entity asynchronously   store.scheduleCreateEntity(newEntity, "cms_Data",
  requestListener, null);
  }
   catch (Exception e){
  e.printStackTrace();
  }
}
public static ODataEntity createEntity(OnlineODataStore store){
  ODataEntity createData = null;
   if(store!= null){
   //If available, it will populates those properties of an OData Entity which are defined by the allocation mode   createData = new ODataEntityDefaultImpl("Model.cms_Data");
   leadData.getProperties().put("Full_Name", new ODataPropertyDefaultImpl("\"Full_Name\"","Deepak Sharma"));
   leadData.getProperties().put("Contact_Number", new ODataPropertyDefaultImpl("\"Contact_Number\"","9810234567"));
    createData.setResourcePath("cms_Data", "cms_Data");
}
return createData;
}
}

 

 

Step 3 : Call openOnlineStore method from your MAFLogon Activity class.


try {
  OnlineManager.openOnlineStore(this,"read");
} catch (ODataException e) {
  e.printStackTrace();
}



Step 4 : Call read and create methods from activity class wherever required with openOnlineStore method.


try {
   OnlineManager.openOnlineStore(this,"create");
   OnlineManager.create(this, "create");
} catch (ODataException e) {
  e.printStackTrace();
}




Hope it helps!!!

SMP 3 Security - Configuration of SAML2 Authentication

 

Topics

 

Introduction

 

SAML support in SMP3 got already introduced in SMP 3 SP05 as a new feature. In the last months I was involved in several customer projects were we implemented SAML authentication.

 

By using SAML you have a great flexibility, because you can use any authentication method (as long as your IDP is supporting it) independent of SMP's predefined security modules.

 

Let's start with a small introduction to SAML. The Security Assertion Markup Language is a defined standard way for authenticating clients. The authentication is performed by a system called "Identity Provider" (IdP). This system is creating "assertion tickets" for the concrete resources the client is requesting. These resource servers are named "Service Providers" (SP). One assertion ticket is always valid for one service provider.


When we now request some resources from a Service Provider we have to authenticate against the IdP first. Because the Service Provider is not authenticating the user (it is "only" validating the assertion ticket coming from IdP), the authentication method is not limited to security mechanism provided by this Service Provider.

 

SAML Process Flow with SMP

 

The SAP Mobile Platform is your Service Provider and is forwarding the user to the IdP for authentication. All communication is performed always between client and SP and between client and IdP, thus there is no direct connection between your SMP and your IdP required. But the client (e.g. the mobile app) needs to have access to the SMP server as well as to the Identity Provider.

 

Following picture should give a basic idea of the client/server communication which is performed if SAML is used as authentication provider.

 

SMP3 Security - SAML2.png

Description of Steps

 

1) Connecting to SMP

The mobile device is connecting to SMP. It does not matter if this is a AppCID registration (first time app execution) or a request to get (or update) data from backend.

 

 

2) Redirecting to IdP

SMP checks the incoming request and searches for a valid session. (A SMP session is identified by cookies X-SMP-SESSID and X-SMP-SESSIDSSO). Because no session is identified, SMP is calling the associated Security Profile. The security profile contains the SAML2 Authentication Provider. SMP is now sending a Redirect (to specified IdP address) to the mobile app. Beside this Redirect the response contains the SAMLRequest object (base64 encoded)

 

 

3) Asking IdP for Authentication

The mobile app is following the redirect and thus contacting the IdP and providing the SAMLRequest.

 

 

4) Responding to AuthRequest

The IdP checks if there is already a valid security (IdP) session. If yes then there would be no need for the client to authenticate again. If no, IdP will ask the user for identifying and authenticating himself.

 

 

5) Identifying and Authenticating the user

This step depends on the security setup on the IdP. Oftne the IdP is displaying a webpage where the user has to authenticate, e.g. by supplying user and password, or a certificate. This step can also be repeated, e.g. if the IdP requests multi factor authentication. So in a first step user has to provide user and password and in a next step a One Time Password (OTP) that got send to the user by using another communication channel (e.g. SMS).

 

 

6) Responding with SAMLResponse

The final response from IdP contains the SAMLResponse object. One part of this SAMLResponse contains the SAML Assertion Ticket and also the destination service provider address is mentioned here.

 

 

7) Posting SAMLReponse to SMP

The mobile app is using the received destination url to send a POST request to sMP (to address ="https://<smp3server : https port>/SAML2/SSO/POST"). SMP's Assertion Consumer Service is listening on this address for the SAML response.

 

 

8) Redireting to originally requested resource

The Assertion Consumer Service is validating the SAMLResponse (check if siganture of IdP is trusted), will then create a new SMP session and send a last redirect to the mobile app. This redirect is pointing to the originally requested resource (from step 1)

 

 

9) Calling the originally requested resource

Same request than in step 1 gets sent again, but this time the session information (SMP session cookies) are attached.

 

 

10) SMP is returning the requested data

SMP Session check is successful and thus SMP will return the requested resource

 

 

SMP SAML2 Configuration

 

  • Generate a Security Provider key pair in Management Cockpit.
  • Use „Generate Key Pair“ functionality in Cockpit
  • Choose a unique Local Provider Name for your SMP (Name of Service Provider)
  • The base url is the hostname and port of the SMP3 load balancer or SMP server

 

image48.png

 

Your SMP server is serving as Local Service Provider.

Click on Get Metadata to receive the metadata document which has to be imported into the IdP…

NOTE: This local SP metadata will need to be imported and configured to the IDP. This will have to be done for each unique SMP installation.

 

In tab Trusted Identity Provider click on „New“ and import your IdP metadata document. This is an XML file that you can receive from your IdP.

 

image49.png

image50.png

Create a new security profile and add SAML2 as Authentication Provider

 

image51.png

 

Note: The Identity Provider Name must match the Trusted Identity Provider name (configured in last step)

 

Testing SAML Authentication with SMP3

 

SSOCircle is providing a free IDP that can be used to test SMP’s SAML flow..

 

  1. Go to https://idp.ssocircle.com/sso/UI/Login?gx_charset=UTF-8 and register
  2. Login or Register
  3. Choose Manage Metadata > SSOCircle Public IDP Metadata.
  4. Save IDP metadata as xml file
  5. Import IDP metadata into Management Cockpit > Settings > SAML > Trusted IDP
  6. Export SMP Service Provider metadata (Settings > SAML > Local Service Provider > Get Metadata)
  7. In SSOcircle Metadata webpage click on „Add new Service Provider“

image53.png

 

8. Create new app in Management Cockpit and assign (new) SAML security provider

(where Authentication Provider set to SAML2 with IDP Name: http://idp.ssocircle.com

See: http://scn.sap.com/docs/DOC-62329 for more information

image51.png

 

Testing SAML Authentication in Browser REST Client

 

SAML can be tested inside the browser, because the browser understand the redirects and can keep cookies in his session…

We want to create a new registration on SMP server and want to use SAML (as described in the previous sections) for authentication.

 

Open registration URL in Chrome:

https://<smp3 server>:<https port>/odata/applications/latest/<appid>/Connections

Concrete sample url could look like this:
https://dewdfwssp2888.dhcp.wdf.sap.corp:8081/odata/applications/latest/com.sap.mit.samltest/Connections

 

This will redirect you to IdP Login Page.

  image54.png

 

After login, IDP will redirect back to SMP (you should see HTTP 501)

 

image55.png

Now we got a SMP session already, because we did not send a POST Request to SMP, SMP responds here with an HTTP501. So let us send a concrete POST request from a REST client. Because we now have a SMP3 session we should be able to create a new registration.

 

Open a REST client (e.g. POSTMAN) and send the POST registration request (because we have now a security context/session there is no need to provide any credentials and SMP server should response with HTTP 201)

 

POST: https://<smp3 server>:<https port>/odata/applications/latest/<app id>/Connections

 

Header:

Content-Type: application/json

 

Body:

{"DeviceType":"Android"}

 

image56.png

 

You are now successful onboarded with SMP by using SAML…

 

 

Backend Connection

 

When choosing SAML for authentication you might also have to think about a single sign on solution for connecting to the backend. Because of the large diversity of backend systems there is no solution available that is working for every backend.

 

Possible SSO scenarios (especially in connection with SAML) are:

 

 

 

Logo.png

SMP 3 Security - Principal Propagation

 

Topics

 

Overview

 

Principal Propagation is a Single Sign On possibility. When a user got already authenticated SMP can use Principal Propagation to create a temporary user certificate and then use this user certificate for connecting to the backend system. The backend system would then create a session/context based on the user certificate. SMP needs for this a CA certificate that can be used to create X509 user certificates. It can be configured how the distinguished name of the user certificates should look like. Usually SMP would insert the user name (of the currently active SMP session) as the common name (cn). This temporary created user certificate has a limited lifetime (e.g. 10 minutes) and will be transferred in a header called SSL_CLIENT_CERT to the backend system. The backend system needs to be setup for client certificate authentication, SMP needs to be trusted as well as SMP's CA certificate. Additionally the connection between SMP and the backend should be secured by mutual https connection.

 

The process is described in following picture:

 

SMP3 Security - Principal Propagation.png

 

1) Data Request

A request is sent to SMP, e.g. a GET Request to receive some data from backend system

 

2) SMP Authentication

SMP will authenticate the user by applying the specified Authentication Providers. Inside the Security Profile there also needs to be defined the Principal Propagation Authentication Provider. the Principal Propagation Module cannot be used alone inside the Security Profile. It must be used in combination with another Authentication Provider that is authenticating the user (e.g. HTTP/HTTPS Authentication, LDAP, SAML2, ...)

 

3) Temporary User Certificate Creation

The Principal Propagation Module will then get the username (of the already authenticated user) from the current session and will create a user certificate (by using a specified CA certificate from SMP's keystore)

 

4) Request to Backend System

SMP will establish a mutual https connection to the backend system. That means, that SMP will use a (beforehand defined) client certificate to connect to the backend system. After that SMP attaches the created X509 user certificate as base64 encoded string into the header SSL_CLIENT_CERT and forwards the request to the backend system.

 

5) Backend System

The backend system receives the request and recognizes that a user certificate is given inside the SSL_CLIENT_CERT header. Because SMP got setup as trusted system and also the CA certificate is trusted, the backend system is using this user certificate to create a user session. There has to be a user/certificate mapping in place, so that the user certificate can be mapped to a concrete existing backend user.

 

6) Data Response

Backend system has now a valid session and will return the requested data

 

7) Data Response

SMP will forward the data response to the mobile device.

 

 

 

Preparation

 

In the following I will described the process for Principal Propagation in combination with a SAP Gateway.

 

Make sure that SSL is enabled on the Gateway server. Steps for this are described for example in following post:
Enabling SSL (HTTPS) on SAP Gateway .


Also test before if your Gateway system is prepared for certificate based authentication. Required steps are described here:

Configuring Client Certificate Authentication (mutual https) on SAP Gateway

 

Steps

 

  1. Create (technical) user for SMP on Netweaver Gateway (in my case SMPCLIENT)
  2. Create a user certificate for SMP (that is accepted by Netweaver Gateway)
    Test it by using transaction CERTRULE and importing SMP user's certificate
    image57.png

  3. Create a CA certificate and key which can be used by SMP to create/sign temporary user certificates.
    Check if certificates created by this CA keypair are really trusted (and mapped) in Netweaver Gateway. If not create a new rule in CERTRULE
    (In my case my CA is called PrincipalPropCA)
    image58.png
  4. Import both cert/key pairs (smpClient and PrincipalPropCA)  as .p12 files into SMP’s keystore
    e.g. by using KeyStoreExplorer or since SP08 by using the Certificate tab in Management Cockpit
    SMP3_Certificates_Management_Cockpit.png
    Remember the given alias names (required in later steps)
    You should stop SMP server, then change the keystore and then restart the server again
  5. 5. Import PrincipalPropCA into your Server PSE in STRUST
      image60.png
  6. 6. Add to the Gateway’s profile parameter the following parameters (in RZ10)
    (Server Restart required)
    Without these values SAP Gateway will not accept the user certificates sent inside the SSL_CLIENT_CERT header.

Parameter

Value

Description

icm/HTTPS/trust_client_with_issuer

EMAIL=marvin.hoffmann@sap.com, CN=MyCA, OU=MIT, O=SAP, L=Walldorf, SP=NRW, C=DE

CA cert of SMP client

icm/HTTPS/trust_client_with_subject

CN=smpClient, OU=MIT, O=SAP, L=Walldorf, SP=BW, C=DE

SMP client certificate

Values can be found inside the client certificate that is used by SMP to establish a mutual https channel to Gateway.

Tip: I recommend checking the exact values inside dev_icm log, because the string has to match exactly the sender, so e.g. if a space is missing it is not working. Simply execute it once, and check with ICM Trace set to 2. (see Debugging section)

 

Using SMP’s base Certificate

 

For testing scenarios you can also use smp’s base certificate: smp_crt
It is also tagged as CA certificate. You could use it for both (smp client as well as principal propagation CA).

image62.png

 

But this should be only used for testing/development purpose...

 

 

SMP Configuration

 

Quick Overview

  • Security Profile contains two Authentication Providers
    (Principal Propagation is not authenticating the user)
  • The Subject Pattern in Principal Propagation Auth Provider Settings needs to be setup in the backend system as user mapping (usually Subject DN of X509 certificate)
  • In Backend Configuration add the certificate alias which identifies a certificate (private key required) in SMP‘s keystore that is used by Principal Propagation Auth Provider to create the temporary user certificate
  • In Backend configuration add „X509“ as SSO mechanism
  • After the user got authenticated (by the first auth provider) the Principal Propagation Auth Provider will create a temporary user certificate for this specific user. This temporary user certificate will be attached as Header SSL_CLIENT_CERT to request against the backend
  • The communication between SMP and backend should be encrypted by mutual HTTPS

 

Steps

 

Create a new app which points e.g. to an odata service from netweaver gateway. Choose as SSO mechanism “X.509”. Additionally you have to set the “Certificate alias”. Here you have to specify the alias name of the key pair which will be used to establish the mutual authentication channel between SMP and backend.

image63.png

 

Additional information to SSO Mechanism (from SAP Help): X.509 Certificates

  • Connects to the back end using the configured technical user X.509 certificate. The end-user certificate is passed in the SSL_CLIENT_CERT HTTP header. Configure the back end to allow the technical user to impersonate the end user and execute the request in the context of the end user. The end-user certificate may be generated by the Principal Propagation provider that is configured in the security profile, or it may be supplied by the end user when he or she authenticates to the server over a mutually authenticated HTTPS connection. You can use this mechanism with either the X.509 authentication provider or the Principal Propagation provider that is configured in the security profile.

 

Add a new security profile. Inside the Security Provider use an authentication provider to authenticate the user (in my case SAML2), then add Principal Propagation module as second provider

 

image64.png

image65.png

The Subject Pattern of the user certificate can be chosen. ${name} is the variable which gets replaced during runtime with the current authenticated username. This user certificate will be valid for 10 minutes only. It will be signed by PrincipalPropCA (which is a CA certificate pair that we imported into SMP’s keystore.

 

image66.png

 

Debugging - ICM Logging/Tracing

 

Especially if communication between SMP and Gateway is not working, it makes sense to have a closer look on what exactly has been received on Netweaver side. For that you can increase the log level of ICM and then use the dev_icm log to check what has been received.

 

Open transaction SMICM and choose Goto > Trace Level > Set

 

image82.png

And set it e.g. to "2 Full Trace"

image83.png

After that execute a failing request, then check ICM Trace File:

image84.png

 

E.g. the following error is thrown, if SMP is not setup correctly as trusted system on SAP gateway: Intermediary is NOT trusted / Reject untrusted forwarded certificate

HttpModGetDefRules: intermediary is NOT trusted -> remove SSL header fields

Reject untrusted forwarded certificate (received via HTTPS with untrusted certificate): subject="

HttpModHandler: remove incoming ssl header

image87.png

So you can see here, that the DN of SMP's client certificate is not exactly the same as that one we are trusting (missing spaces...). So we need to correct this in transaction RZ10:

image61.png

 

 

Logo.png

In my first blog on mobile development about a year back

SAP and Xamarin for C# Mobile Cross Platform Development Tutorial

I had described in detail about mobile application architecture.  All my earlier applications were developed  in C# using Xamarin.  When Swift was released I wanted to explore this and write a iphone app with based on Swift and Xcode 6.3. This blog

describes the SAP Plant Stock application that I developed using SWIFT.  The ABAP side REST service has been developed as described in my earlier blog.

When I started my application I wanted to provide all the functionality that I had provided with a similar app that I had developed for Andorid and Iphone using Xamarin(C#) but develop this in SWIFT and Xcode 6.3

Xamarin Native IPhone and Apple Watch for Plant Stock - Part1

I Created a new Single View Application using Xcode  - Deleted the created View Controller - Added a new Table View Controller - Embedding this in Navigation View Controller  (Edit Menu- Embed in Navigation View Controller)

image1.png

Create Swift Classes one based on Table View Controller and other UI View Controller and assoicate these classes with storyboard.  Create a Segue way from the barbutton to the second View Controller which will allow the user to enter the selection - For the Material I have textFiled for the Plant and Location I populate two pickers from the data I get from SAP REST call.

 

To Make REST all to SAP I Created a protocol as follows

protocol SapRestProtocol {

    func didRecieveResponse(results: NSArray)

}

as I wanted to get the master data for Plants and locations at the start I changed the appdelegate file to implement the above protocol to read

class AppDelegate: UIResponder, UIApplicationDelegate, SapRestProtocol {

{

        // Override point for customization after application launch.

        checkandFetch()

        return true

  }

}

    func checkandFetch()

    {

       /

 

         var sapapi: SapRest? = SapRest()

         sapapi!.delegate = self

  sapapi!.GetSapMasterData("XXXXX",  RestTag: "/MADATA/MADATA")

}

 

    func didRecieveResponse(results: NSArray) {

      

        if results.count>0 {

     //populate the location and plant arrays which have to populated in the respective pickers

}}


in the SapRest Class I have code as follows

class SapRest: NSObject {

    var data: NSMutableData = NSMutableData()

    var delegate: SapRestProtocol?



func GetSapMasterData(stringPost: String, RestTag:String) {

       

     

        let path =  ""

        let  SapClient = ""

        let userName = "

        let passWord =  ""

        let loginString = NSString(format: "%@:%@", userName, passWord)

        let loginData: NSData = loginString.dataUsingEncoding(NSUTF8StringEncoding)!

        let base64LoginString = loginData.base64EncodedStringWithOptions(NSDataBase64EncodingOptions.allZeros)

        let urlPath = path + RestTag + SapClient;

 

            if let url = NSURL(string: urlPath) {

               

                var request: NSMutableURLRequest = NSMutableURLRequest(URL: url)

                request.HTTPMethod = "POST"

                request.setValue("Basic \(base64LoginString)", forHTTPHeaderField: "Authorization")

                var postData:NSData = stringPost.dataUsingEncoding(NSUTF8StringEncoding)!

                var postLength: NSString = String(postData.length)

                request.HTTPBody = postData

               

                request.setValue(postLength as String, forHTTPHeaderField: "Content-Length")

                request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")

                request.setValue("application/json", forHTTPHeaderField: "Accept")

               

          

                if let connection = NSURLConnection(request: request,delegate: self,startImmediately: false) {

                   

                 

                    connection.start()

                }

            }

        }

       

    //}


We now have the Plants and Locations in two arrays of strings so that when the user clicks on the Button - we will populate the pickers and transfer to the selection screen.

For the TableView Controller I have code - we have to implement the functions to implement the required functions


class StockListController: UITableViewController {


 

    override func numberOfSectionsInTableView(tableView: UITableView) -> Int {

        return 1

    }

 

   

    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

   

        return items.count

    }

   

    @IBAction func unwindToStockList(segue: UIStoryboardSegue)

    {

        if segue.identifier == "UnWindList" {

            if let svc = segue.sourceViewController as? SelectViewController {

                in_rec = svc.indata

                if in_rec.MATNR != "" {

                    getData()

                    self.tableView.reloadData()

                }

            }

        }

       

    }

 

   

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

        var cell = tableView.dequeueReusableCellWithIdentifier("StockCell", forIndexPath: indexPath) as? StockCell

       

        if cell == nil {

          

            cell = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier: "StockCell") as? StockCell

           

        }

 

        var celldata = items[indexPath.row];

        // Configure the cell...

        cell!.txtPlant!.text = celldata.WERKS

        cell!.txtLocation!.text = celldata.LGORT

        cell!.txtBatch!.text = celldata.CHARG

        cell!.txtStock!.textString(format: "%d",celldata.LABST)

       

       

 

        return cell!

    }

Notice the UnWind usage - this comes into action when the user selects the parameters and cliks on Get Button - it is at at that time GetData will call SAP and get the stock records based on the Selection Parameters


Now let us look the code where the user is allowed to enter the selection criteria and then click on Get - For the material the user can scan for barcode

import UIKit

import AVFoundation

 

 

class SelectViewController: UIViewController, UITextFieldDelegate, UIPickerViewDelegate, UIPickerViewDataSourceScannerViewControllerDelegate {

    var scannerVC: ScannerViewController!

...

you have to implement the following delegate and datasource methods


func textFieldShouldReturn(textField: UITextField) -> Bool {

       textField.resignFirstResponder()

        return false

    }

   

    func SetDataForPicker( PLANTS:[String], LOCATIONS:[String])->() {

        self.plants = PLANTS

        self.locations = LOCATIONS

    }

    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {

        if segue.identifier != "UnWindList" {

          scannerVC = segue.destinationViewController as! ScannerViewController

          scannerVC.delegate = self

        }

    }

override func viewWillAppear(animated: Bool) {

     

       self.pickerPlant.reloadAllComponents()

       self.pickerLocation.reloadAllComponents()

        selPlant = "ALL"

        selLocation = ""

    }

   

 

    @IBAction func btnScan(sender: AnyObject) {

      

      

    }

   

    //---fired when the barcode is captured---

       

    @IBOutlet weak var pickerPlant: UIPickerView!

  

   

    @IBOutlet weak var pickerLocation: UIPickerView!

   

   

    override func didReceiveMemoryWarning() {

        super.didReceiveMemoryWarning()

        // Dispose of any resources that can be recreated.

    }

   

    @IBAction func btnGetStock(sender: AnyObject) {

        if selPlant == "ALL" || selPlant == "All" {

            selPlant = ""

        }

        if selLocation == "ALL" || selLocation == "All" {

            selLocation = ""

        }

        if txtMaterial.text.isEmpty {

           

        let alert = UIAlertController(title: "No Material",

                message: "Please Enter Material and Click Get Button",

                preferredStyle: .Alert)

           

            // Display the alert

            self.presentViewController(alert,

                animated: true,

                completion: nil)

            return

        }

       // if let parentc = self.parentViewController as? StockListController {

            indata.MATNR = txtMaterial.text

             indata.WERKS = selPlant

             indata.LGORT = selLocation

             indata.CHARG = selbatch

           

     

       

    }

   

 

  

    func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int {

       return 1

    }

}


We have to implement the required delegates - The picker data is populated when you click on the tool bar button -

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {

        // Get the new view controller using [segue destinationViewController].

        // Pass the selected object to the new view controller.

        if segue.identifier == "ToSelect" {

            let vc = segue.destinationViewController as! SelectViewController

            var appdeg = UIApplication.sharedApplication().delegate as! AppDelegate

            vc.SetDataForPicker(appdeg.plants,  LOCATIONS: appdeg.locations)

 

        }

        else if segue.identifier == "StockDetail" {

              let vc = segue.destinationViewController as! StockDetailViewController

            if let currpath =  tableView.indexPathForSelectedRow(){

                vc.datarec = items[currpath.row]

            }

           

        }

    }


In addition I have one more view controller that displays the detailview when you click on TableLine -(segue.identifier == "StockDetail") - Just as I was finishing I upgraded my Iphone to 8.4 - and then I had to upgrade to Xcode 6.4 so that I can test this app on my phone. I have used the same Architecture as my mobile applications - There is lot material on SWIFT/XCODE so I wanted to emphasize on the SAP interfacing part.


image100.jpg




 




With SP08 release, the OData SDK introduced block based completion handler APIs. The new APIs are used to open the online store and for scheduling execution requests.

 

Before we go into the specific APIs, let us take a quick look at blocks in iOS. In a nutshell, blocks are segments of code that can be passed to methods, just like you would pass values.Quoting from https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/WorkingwithBlocks/WorkingwithBlocks.html:

 

“A block represents a distinct unit of work, combining executable code with optional state captured from the surrounding scope.”

 

 

One of the most common uses of blocks are as completion handlers. As specified in https://developer.apple.com/library/ios/featuredarticles/Short_Practical_Guide_Blocks/, completion handlers are callbacks that allow a client to perform some action when a framework method or function completes its task.Consider a function that needs to make a callback at a later time. To be able to use a block as a completion handler, the block must be passed as an argument to the function, so that when it finishes execution, the block is simply called. It is essential that the block is the last parameter of the function.

 

For example, here is the declaration of a function:

 

-(void)someFunction:(int)paramOne  andCompletion: (void(^)(NSString* blockParam))completionBlock;

 

Below is the function implementation:

 

-(void)someFunction:(int)paramOne andCompletion: (void(^)(NSString * blockParam))completionBlock{

     NSString *msg = @"Example of using blocks as completion handlers.";

     completionBlock(msg);

}

 

Consider the code below where the above function is invoked:

 

[self someFunction:5 andCompletion:^( NSString * blockParam){

 

// Define the block code here, thus only when ‘someFunction’ is invoked, the completion

// handler block is defined.

NSString *finalString = [NSString stringWithFormat:@"%@Blocks make life easy.",blockParam];

NSLog(@"%@",finalString);

}];

 

 

The final string printed out :

Example of using blocks as completion handlers. Blocks make life easy.

 

With the above basics, lets take a closer look at the block based API for the store opening. The block API for opening the store  is :

 

-(void) openStore:(void (^)(id<SODataStoreAsync> store, NSError* error)) completionBlock;

 

A call to openStore using this API would be as follows:

 

SODataOnlineStore* onlineStore = [[SODataOnlineStore alloc] initWithURL:[NSURL URLWithString:self.endpointUrl] httpConversationManager:self.logonHandler.httpConvManager];

 

[onlineStore openStore:^(id<SODataStoreAsync> store, NSError *error) {

if(!error)

{

     [self getODataEntries];

} else {

      NSLog(@"%@",error.description);

}

}];

 

 

If the store was opened via the old delegate based APIs ( as shown below) , we would need to use the SODataOnlineStoreDelegates methods. As you can see the code with blocks is much succinct and readable than using delegates.

 

SODataOnlineStore* onlineStore = [[SODataOnlineStore alloc] initWithURL:[NSURL URLWithString:self.endpointUrl] httpConversationManager:self.logonHandler.httpConvManager];

 

[onlineStore setOnlineStoreDelegate:self];

NSError *error = nil;

BOOL success = [onlineStore openStoreWithError:&error];

 

#pragma mark - SODataOnlineStoreDelegates

-(void)onlineStoreOpenFinished:(SODataOnlineStore *)store{

     [self getODataEntries];

}

-(void)onlineStoreOpenFailed:(SODataOnlineStore *)store error:(NSError *)error{

     NSLog(@"%@",error.description);

}

 

The SDK also provides a list of completion block based network request APIs. ( Check the ODataOnlineStore.h header file for the full list.)  These APIs in particular eliminate a lot of application specific code that are required when using delegates. eg. When we use the SODataRequestDelegate methods, in each method such as the requestFinished: method, there is no need to query which kind of request was invoked ( create , update or delete) , the collection being queried etc.

 

Below is an example of an update request with completion handler block APIs and the delegate APIs for comparison. The block based code is much easier than the requestFinished: delegate method where we have to verify if the request mode was indeed update before proceeding to process the result.

 

Block based update request:

 

[onlineStore scheduleUpdateEntity:entity options:nil completion:^(id<SODataRequestExecution> requestExecution, NSError *error) {

    if (!error)

            NSLog(@"Update Successful");

}];

 

 

 

Delegate based update request:

 

 

[onlineStore scheduleUpdateEntity:entity delegate:self options:nil];

 

#pragma mark - SODataRequestDelegates

 

- (void) requestFinished:(id<SODataRequestExecution>)requestExecution {

NSError * error = nil;

id<SODataRequestParam> requestParam = requestExecution.request;

id<SODataRequestParamSingle> request = (id<SODataRequestParamSingle>)requestParam;

    

if ([requestParam conformsToProtocol:@protocol(SODataRequestParamSingle)])

{

     if (request.mode == SODataRequestModeUpdate)

     {

      if (!error)

          NSLog(@"Update Successful");

      }

}

}

 

Based on the above examples , using completion-block based APIs has the following advantages:

 

  • There is no need to adopt protocols, or implementing delegate methods. This results in reduced code.

 

  • Since the completion handler block code is always defined when the method is invoked, it results in simpler, more readable code. Additionally, the completion handler can directly access variables existing locally in the scope where it is defined.

 

  • The completion-block based network request APIs in the SDK are easier to implement as they require less context–specific checks as with the delegate methods.

 

One final but very important point - you could choose to use the delegate or the block based APIs in your app, but do not mix the two flavors.

I hope this blog provided a good introduction to the completion-block APIs and help you take advantage of it in your next app.


See you in the next blog!

Hi there,

 

A few weeks ago I created the following post: SAP Work Manager 6.2.0: How To Add Pictures to a Local Notification (using BDS)

 

In this blog, I will describe how to implement the closely related functionality that allows the user to have offline access to all the BDS notification pictures (or documents) after executing the transmit action.

 

Be careful with this because it can cause huge performance issues since we will retrieve the content of all the BDS documents linked to the user notifications. In my scenario, most of the documents, manuals and other work order related material are assigned via DMS and retrieved in a different way out of Agentry. Only the notification pictures are BDS attachments, therefore fetching the content won't cause too much trouble.

 

The Problem:

 

Work Manager 6.2.1 introduces a new functionality to download attachments via synchronous call instead of the push request fetch that is causing a lot of problems to developers implementing the solution. If you are running Work Manager 6.2.0 there is a separate note to implement this: http://service.sap.com/sap/support/notes/2142473

 

But even with this enabled, the documents are only retrieved under user request, so if there is no connectivity it won't be possible to fetch the attachment content. In my scenario, field technicians are working mostly offline and they need to have all the attachments available once the transmit finishes and they leave the facilities with internet access.

 

Development Overview:

 

  • The goal is to fetch the notification attachments during the transmit action. Since there is already a standard BAPI available to fetch the content of a document link, ABAP development won't be necessary.
  • For a better performance and in order to use the Agentry Exchange Mechanism, the document content will be retrieved during the notification fetch process and stored in a session component manager for Document Links. Later on an Agentry fetch will get the documents from the component manager without calling the back end again.
  • Prerequisite. Only valid for Work Manager 6.2.1 or implementing note 2142473 for 6.2.0

 

Agentry UI changes:


The Agentry changes are minimum. Only a new fetch executed every transmit has been created with its corresponding steplet.


  • Created new Java steplet DocumentLinksFetchOnTransmitSteplet calling the adhoc created class com.syclo.sap.workmanager.customer.steplet.DocumentLinksFetchOnTransmitSteplet

 

  • Created new fetch ZDocumentLinksFetchOnTransmit
    • Run on trasmit
    • Add steplet DocumentLinksFetchOnTransmitSteplet as a Server Exchange Step



 


Java changes:

 

There are multiple Java changes divided in 2 separate concepts. The 1st one is to fetch and store the notification document links with the binary content into the DocumentLinks component manager during the notification fetch execution. The 2nd one is the transmit steplet executed during the newly created Agentry Fetch that will retrieve the previously stored Document Links to populate the Agentry collection.

 

1. Store Document Links with content to the component manager. Classes Notification and WorkorderNotification have to be extended

 

 

  • Created static class com.syclo.sap.workmanager.customer.DocumentLinkUtils in order to have a reusable method to fetch the document link content
    • Added constants to access a new configuration portal parameter that enables this functionality
public static final String ATTACHMENT_SYNCH_ENABLE_SECTION = "APPLICATION_CONFIG";
    public static final String ATTACHMENT_SYNCH_ENABLE = "ZAttachment.SynchOnTransmit";





    • Added method isFetchOnTransmitEnabled to check if the functionality is ON
public static boolean isFetchOnTransmitEnabled(User user) throws AgentryException{
        return user.getPropertyValue(ATTACHMENT_SYNCH_ENABLE_SECTION, ATTACHMENT_SYNCH_ENABLE).equalsIgnoreCase("Y");
    }





    • Added method getDocumentLinkContentToComponentManager(DocumentLink, User) to get the binary data of a given Document Link.

 

public static void getDocumentLinkContentToComponentManager(DocumentLink dl, User user){
  
        Logger log = new Logger(user, "getDocumentLinkContentToComponentManager");
  
        try{  
            DocumentLinkComponentManager documentLinkCompManager = (DocumentLinkComponentManager)user.getComponentManager(DocumentLinkComponentManager.class.getCanonicalName());
      
            if(dl.isBDS() && dl.getFileMimeType().matches("image/(.*)")){              
          
                ArrayList<? extends SAPObject> recs = null;
                dl.setDocID(dl.getID());
                String agentryLastUpdate = user.eval("<<lastUpdate format=\"%m/%d/%Y %H:%M:%S\">>");
                GregorianCalendar lastUpdate = ConversionUtility.getTimeStampFromString(agentryLastUpdate, ConversionUtility.TIMESTAMPFORMAT);
          
                //Call the BAPI to read the binary data for the document
                BDSDocumentSynchFetchBAPI bapi = (BDSDocumentSynchFetchBAPI) BAPIFactory.create("BDSDocumentSynchFetchBAPI",
                        new Class<?>[] {User.class, GregorianCalendar.class, SAPObject.class},
                        new Object[] {user, lastUpdate, dl});                  
                bapi.run(dl);
                recs = bapi.processResults();
          
                if(recs.size() > 0){
                    //Store the retrieved document to the component manager,
                    //DocumentLinksFetchOnTransmitSteplet will access to the data afterwards
                    //documentLinkCompManager.addObject(recs.get(0));                      
                    Object val = recs.get(0);
                    if (val instanceof DocumentLink){
                        DocumentLink documentWithContent = (DocumentLink)val;
                        documentLinkCompManager.addObject(documentWithContent);
                        log.info("::Document ID = " + documentWithContent.getDocID());
                    }  
                }
            }
        }catch(Exception e){
            log = new Logger(
                    Server.getSAPServer(),
                    "com.syclo.sap.workmanager.customer.DocumentLinkUtils" +
                    "::getDocumentLinkContentToComponentManager(dl,user)::Thread " +
                    Thread.currentThread().getId() + "::");
            log.error(e.getMessage());
            e.printStackTrace();
        }
    }





 

  • Created class com.syclo.sap.workmanager.customer.object.Notification extending from the standard.
    • Override method addDocumentArrayList(ArrayList<DocumentLink> linkedDocuments) in order to retrieve each document link content and store it into the corresponding component manager to previously populate the objects collection in Agentry with an independent fetch.

 

 

@Override
    public void addDocumentArrayList(ArrayList<DocumentLink> linkedDocuments){
  
        Logger log = new Logger(_user, "addDocumentArrayList");
  
        try{
            if(linkedDocuments.size() > 0 && DocumentLinkUtils.isFetchOnTransmitEnabled(_user)){
                Iterator<DocumentLink> it = linkedDocuments.iterator();          
                while(it.hasNext()){
                    DocumentLink dl = it.next();
                    DocumentLinkUtils.getDocumentLinkContentToComponentManager(dl, _user);
                }
            }
        }catch(Exception e){
            log = new Logger(
                    Server.getSAPServer(),
                    "com.syclo.sap.workmanager.customer.object.Notification" +
                    "::addDocumentArrayList(linkedDocuments)::Thread " +
                    Thread.currentThread().getId() + "::");
            log.error(e.getMessage());
            e.printStackTrace();
        }
        super.addDocumentArrayList(linkedDocuments);  
    }





 

 

  • Created class com.syclo.sap.workmanager.customer.object.WorkorderNotification extending from the standard.
    • Override method addDocumentArrayList(ArrayList<DocumentLink> linkedDocuments) in order to retrieve each document link content and store it into the corresponding component manager to previously populate the objects collection in Agentry with an independent fetch. The code is exactly the same than the used for the Notification object.

 

 

 

  • Created class com.syclo.sap.workmanager.customer.bapi. BDSDocumentSynchFetchBAPI extending the standard
    • Skip reading the document link ID from the fetch properties if the value is not initial. This is necessary since we are not calling the BAPI directly from the Agentry fetch as the original assumes. Now it’s called from the Notification/WorkOrder fetch and the properties are already set in the BAPI class _documentLink attribute.

 

 

@Override
    public void init() throws Exception {
        //Only call super init() if _documentLink.ID is initial
        if(_documentLink.getDocID().equalsIgnoreCase("")){
            super.init(); //Gets the ID from the Agentry fetch
        }
     }


 

2. Steplet executed by the newly created Agentry fetch to populate document links into the Agentry collection.

 

  • Created class com.syclo.sap.workmanager.customer.steplet. ZDocumentLinksFetchOnTransmitSteplet
    • The steplet class will be called by the Agentry fetch ZDocumentLinksFetchOnTransmit  during transmit.
    • The steplet will call a new step handler class called ZDocumentLinksFetchOnTransmitStepHandler

 

@Override
    public boolean execute() throws AgentryException {
        try {
            ZDocumentLinksFetchOnTransmitStepHandler handler = (ZDocumentLinksFetchOnTransmitStepHandler) StepHandlerFactory.create(
                    StepHandlerFactory.getClassName("ZDocumentLinksFetchOnTransmitStepHandler"),
                    new Class[] { com.syclo.sap.User.class },
                    new Object[] { _user });
            ArrayList<SAPObject> recs = handler.run();
            int sz = recs.size();
            if (sz > 0) {
                _returnData = createSAPObjectArray(getSAPObject(), recs);
            }
            return true;
        }
        catch (Throwable e)
        {
            throwExceptionToClient(e);
            return false;
        }
    }






 

  • Created class com.syclo.sap.workmanager.customer.stephandler. ZDocumentLinksFetchOnTransmitStepHandler
    • This step handler class retrieves the document links from the corresponding component manager only if the parameter Attachment.Synchronous is enabled

 

public class ZDocumentLinksFetchOnTransmitStepHandler extends StepHandler {
    public static final String COMPONENT_MANAGER_CLASS_NAME = DocumentLinkComponentManager.class.getCanonicalName();
    protected DocumentLinkComponentManager _compManager;
    public ZDocumentLinksFetchOnTransmitStepHandler(User user) throws Exception {
        super(user);
        _compManager = (DocumentLinkComponentManager)_user.getComponentManager(COMPONENT_MANAGER_CLASS_NAME);
    }
    /**
     * Get the list of Document Links for the user by reading from the component manager
     * table associated with the user object. This method does not call any BAPI. This method
     * assumes that the user's hashtable has been populated previously from Notification and/or
     * Work Order fetch
     *
     * @return list of DocumentLink objects populated in fetch
     * @throws AgentryException
     */
    public ArrayList<SAPObject> run() throws AgentryException {
        String methodLabel = "run";
        Logger log = new Logger(_user, methodLabel);
        ArrayList<SAPObject> recs = new ArrayList<SAPObject>();
        if (DocumentLinkUtils.isFetchOnTransmitEnabled(_user)) {
            _user.inFetch(true);
            Hashtable<String, DocumentLink> headers = _compManager.getObjects();
            Enumeration<?> e = headers.elements();
            while (e.hasMoreElements()) {
                Object val = e.nextElement();
                if (val instanceof DocumentLink) {
                    DocumentLink dl = (DocumentLink)val;
                    recs.add(dl);
                    log.info("::Document ID = " + dl.getDocID());
                }
            }
        }
        log.info("::end");
        return recs;
    }
}






 

 

 

Configuration Portal Adjustments:

 

Finally, some adjustments have to be done in the configuration portal for the newly created classes and parameters

 

  • Created a client global to enable or disable the new functionality
    • Group: APPLICATION_CONFIG
    • Name: ZAttachment.SynchOnTransmit
    • Value: Y

 

  • Change the global parameter to assign the class for Notification object in order to use the new customer class
    • Group: SAPOBJECT
    • Name: Notification
    • Value: com.syclo.sap.workmanager.customer.object.Notification

 

  • Change the global parameter to assign the class for Work Order Notification object in order to use the new customer class
    • Group: SAPOBJECT
    • Name: Notification
    • Value: com.syclo.sap.workmanager.customer.object.WorkorderNotification

 

  • Created global parameter to assign the ZDocumentLinksFetchOnTransmitStepHandler step handler class dynamically.
    • Group: STEPHANDLER
    • Name: ZDocumentLinksFetchOnTransmitStepHandler
    • Value: com.syclo.sap.workmanager.customer.stephandler. ZDocumentLinksFetchOnTransmitStepHandler

 

  • Changed global parameter to assign the customer BDSDocumentSynchFetchBAPI class
    • Group: BAPI_CLASS
    • Name: BDSDocumentSynchFetchBAPI
    • Value: com.syclo.sap.workmanager.customer.bapi. BDSDocumentSynchFetchBAPI

 

  • Created global parameter to assign a BAPI wrapper to the new customer BDSDocumentSynchFetchBAPI class
    • Group: BAPI_WRAPPER
    • Name: com.syclo.sap.workmanager.customer.bapi. BDSDocumentSynchFetchBAPI
    • Value: /SMERP/CORE_DOBDSDOCUMENT_GET

In Part1

http://scn.sap.com/community/developer-center/mobility-platform/blog/2015/06/12/xamarin-native-iphone-and-apple-watch-for-plant-stock--part1

I had developed Xamarin IOS native app for Plant Stock.  Here we will extend the app to develop  Apple Watch app to provide a similar functionality.

In the Phone app I could enter or scan the Material Code - There is no input Text Box where for you can  enter on the watch - There are table controls that you can scroll and Pick.  For this Watch App I ddi not want any material to be used and further I don't want the user to select Plant and Batch - The list of Materials that Watch app user can Choose are determined in SAP - I develop a REST call that will return a List of Materials that a watch app user can choose.  This can be changed by SM30 in a Ztable on SAP side.

Here are the steps to  create a watch app project using Xamarin

1) Open the Project Created in Part1 in Xamarin Studio

2) Right Click on the solution and choose Add new project and choose IOS - Extension and WatchKitApp

W1.png

When you do this you have 3 projects - In my case I started with project PlantstockIOS in part1 when I added the wachkit app project I have two more projects

A) PlatnstockIOS ( IPhone app created in Part1)

B) PlantstockIOSWatchExtensionApp - The runs on iPhone - When you want to test you have set this as start up project

C)PlantStockIOSWatchKitapp   runs on Watch - This has interface.Storyboard file -

The prjoject C references Project B and B in turn references Project A.  Double click on info.plist file and make sure that  Bundel ids are correct otherwise

the complier will error out.

3)  Select Watchkit  App project (Project C) and double click on interface.StoryBoard file - This opens up Xamarin Watch kit designer.

W2.png

 

 

 

 

4) Add 3 interfaces controls

     a)  Starting interface Controller - Add tablecontrol -give it a name, There is a RowController - Assign it class name and id - Drag a label and give it a name

         This will display the list of materials that the user is allowed to select to fetch the stock records from SAP

    b)  The Second will also be very similar - this will display records giving break down by Plant, Location and Batch - You can Click on this to display a details record

   c)  This interface control had  5 labels that will display Material ,Plant, Location, ,Batch and Stock - If the stock is less than 10 the color will be displayed as red otherwise green

5) When ever you give a name to the controls that you added in storyboard the corresponding Classes are created in the Extension project and you have write the code in the extension project.

6) Some of Events that required coding in the Watch kit interface to make the application happen

     a)  async overrid void WillActivate() - In this I call SAP to get Materialist    - await getSAPMaterial() and load the rows in the first Controller

     b)  overrid void DidSelectRow

            get the row that the user clicked on  - the row index is parameter to this call

     c)  override NSObject GetcontextForSeque

          get the selectedRow (index is parameter for this call) and pass the selected material to the next call

     d) Second Controller -

             override Awake(Context)

                Get the Selected Parameter from the context  material =  context.ToString();

      e) async overide void willActivate()

        await getstock(material) - Call SAP to get the stock records

        bind the records to the table rows

     f)override NSobject GetContextForSegue - Get the selected record to be passed on the next controller

7)  Third Controller Detail Record - Again get the different fields check qty for > 10 and display green other wise red. All this is done in the WillActivate method of this controller

W2.png

 

 

The Current Version of the apple Watch requires phone app (Extension app) - The recently accounced 2.0 does not require phone app - Xamarin has announced the release of preview version which I have not yet explored.   As Apple Watch is becoming popular I feel that for C# developer developing SAP Based Apple Watch should be interesting .

Very Recently I had written a Plant StockNative application using Xamarin for Andorid

http://scn.sap.com/community/developer-center/mobility-platform/blog/2015/05/22/xamarin-andorid-native-application-for-plant-stock-transaction-mmbemobile-version

With all the buzz on apple Watch I wanted to develop Apple Watch application for SAP Plant Stock - I wanted to use the same Backend REST services that I had developed for my earlier android application.

When you want to develop an Apple Watch application ( as of version 1.0 of Apple Watch you have to have an iPhone app and watch app.  The Watch app

communicates with Phone app to get the data which is displayed on the watch.  So I decided to write an IPhone app using Xamarin and then extend this to Apple Watch in (Part 2 http://scn.sap.com/community/developer-center/mobility-platform/blog/2015/06/12/xamarin-native-iphone-and-apple-watch-for-plant-stock--part2).  If only wanted to develop an Watch app alone my Phone app need have any Great UI and handle interactions - As I wanted to have a

fully working iPhone App as well I created a new IPhone Project using Xamarin Studio.

image1.png

I choose a single View Application and double click on Main.storyboard file to open the Xamarin designer and added the tableView controller and couple of view controller, navigation controller and tool bar button -  For detailed description of app development check out on  Xamarin.com, youtube and github.

I have added nugets for SQLite, Newtonsoft json, ZXING bar code component from Xamarin Components - Added references to System.Net.Http.

image2.png

 

When the Appstarts in Appdelegate.cs file I have set up SQLite database that w will save Plant Stock so that you don't have to call SAP for the Same Selection Criteria.    The View Controller that is connected to the navigation controller is displayed on the phone

image3.png

In the Select Screen for the Plant and Location I have two Picker controls that are populated by an REST Call to SAP - For Material and Batch I have to textfields. For the Scan Barcode button I have code that calls ZXING component to scan using Iphone Camera - When you click on the get button -

a REST call is made to SAP to get Stock Records which are populated into an SQLite Database and is displayed on the tableview Controller.  For the table view Controller  I have custom cell with four lables.

1) in the table View Controller - you have to set TableView.Source to a class that implements UITableViewSource Interface methods.

2) When you click on Select the Select View Controller is called - This has two pickers - For the pickers to display the dropdown you have implement UIPickerModel for Plant and Loctions and then assign to the respective picker model.

 

 

3) When the Get Button is clicked make a REST Call to SAP to get the stock records and then call PopViewConrller to get back to the TableView Contoller as the seque that is called when you click the button performs a push.

In the next part I will extend this to work on Apple Watch

It’s that time of the year again… old making way for the new

 

As you probably remember, June is the time-frame when a newer version of Eclipse is added to the fold and the older one is dropped from SAP support. Keeping the same tradition, Eclipse Kepler will be out of support from SAP NetWeaver Cloud by end of June 2015. This also means that Eclipse Luna will be added to the list of supported Eclipse versions for our SAP Development Tools for Eclipse.

 

So what are the implications for SAP Mobile Platform Tools based on Eclipse Kepler?

 

We plan to release the next version of our tools compatible with Eclipse Luna in early July 2015. This will ensure business continuity for developers without having to wait long after Kepler support has ended. We have come a long way since releasing the 1st version of SAP Mobile Platform Tools in July 2014.

 

In 2015 alone we have added the following features to this toolset:

 

  • Jan 2015
    • Support for REST “Read” operation. “Query” was already supported
  • Apr 2015
    • Support for REST “Create”, “Update” and “Delete”. With this we now cover the entire CRUDQ stack
    • Release of Ogee as open source component to Apache foundation and consuming
    • Debugging support for scripts
  • July 2015

 

So please stay tuned for this release on SAP Development Tools for Eclipse page, and as always keep your feedback flowing

 

Thank you!

 

Amit Nigam

Product Management - SAP Gateway

Actions

Filter Blog

By author:
By date:
By tag: