API Authentication Method in ELM – Oauth 1.0a

The goal of the next two posts is to provide you with methods for authentication of your APIs.  I will not be going thru all the methods that a ELM server support authentication, but at a high level your server can either be setup to use application level authentication or to delegate that authentication to some external service.  If you utilize application level authentication your APIs should be using OAuth 1.0a. If you utilize delegated authentication, you will be using a Bearer token with your APIs.

Let’s begin with OAuth 1.0a:

As I pointed out in the blog post on “Beginning API Discovery”, each server exposes a set of key end points which are required as part of OAuth.  Before using those APIs we will need to access our server as an admin and create a key / secret pair, with the appropriate access we will need for our APIs.  

  1. We will log in as elmadmin (utilize an appropriate user ID with server administrative authority on your server), on our Requirements Management Server, and access the Server Admin page.
https://elmwb.com:9443/rm/admin
  1. Select Consumers to access the OAuth Consumers page. At the top of the page we will register a new “Oauth Consumer” and the system will generate a key for us.  We need to provide the following information:  consumer name (this is informational only), a consumer secret (which will be used as part of our OAuth1.0a process), and select trusted.
Server -> Consumers (Inbound)
  1. The system will provide you with the Consumer Key, but you still need to assign a functional user ID. To assign the ID go to the bottom of the page and select the pencil (edit) under actions, and then choose “Select User”.  Search for and select the appropriate user id (elmadmin on my server), choose OK, and select Finish.
Creating a new Consumer
Editing to add User
Search and Select User
  1. Your server is now set and you have all the information to actually authenticate your API calls to the ELM server utilizing OAuth 1.0a.  If you don’t have authority to do the above steps, you will need to work with your server administrator who will provide you with the Consumer Key and Secret, you will need to use in authentication before you API can use OAuth.

What is OAuth?

OAuth is a standard for allowing access to protected resources without disclosing user credentials.  OAuth is defined at https://oauth.net/core/1.0a/ and consists of three steps (as defined in section 6 of the previous link).  For this purposes of this post, I will assume that you have read thru the details of OAuth and are looking for a practical approach of how to implement it with your API flow.

Remember our RootServices

In a prior blog post (Beginning API Discovery) I pointed out the following information being provided in the RootServices document:

<jfs:oauthDomain>https://elmwb.com:9443/jts,https://elmwb.com:9443/rm</jfs:oauthDomain>
<jfs:oauthRealmName>Jazz</jfs:oauthRealmName>
<jfs:oauthAccessTokenUrl rdf:resource="https://elmwb.com:9443/jts/oauth-access-token" />
<jfs:oauthApprovalModuleUrl rdf:resource="https://elmwb.com:9443/jts/_ajax-modules/com.ibm.team.repository.AuthorizeOAuth" />
<jfs:oauthExpireTokenUrl rdf:resource="https://elmwb.com:9443/jts/oauth-expire-token" />
<jfs:oauthRequestConsumerKeyUrl rdf:resource="https://elmwb.com:9443/jts/oauth-request-consumer" />
<jfs:oauthRequestTokenUrl rdf:resource="https://elmwb.com:9443/jts/oauth-request-token" />
<jfs:oauthUserAuthorizationUrl rdf:resource="https://elmwb.com:9443/jts/oauth-authorize" />

For our API process we need three key end points – <jfs:oauthAccessTokenUrl>, <jfs:oauthRequestTokenUrl>, and <jfs:oauthUserAuthorizationUrl>. These API end points support our process as defined above in the OAuth standard link.

There are many libraries and other tools which may make it easier for you to deal with OAuth; however, I’d like to take you thru the entire flow in this post.  This will help you understand how the APIs work, that you just discovered in the RootServices API.

High-level OAuth Flow


Simplified Oauth 1.0a Flow

It looks pretty easy doesn’t it?  Seriously, it will take a bit to break it down.  Using our specific APIs from the RootServices document, I’ll also show you the headers, and how the redirects work.

Step 1 – We begin with the initial Post in the top left of the above image.  This maps to section 6.1 in the Oauth specification for obtaining an unauthorized Request Token.  This token can only be used once, and requires providing your oauth consumer key, oauth consumer secret, a signature method, oauth signature, and a oauth nonce.  We support HMAC-SHA1 for the signature method.  Creating your oauth signature is defined in section 9.1 of the above Oauth standard.  The nonce, is any unique value you wish to provide, as nonce means – “number used once”,  a full time stamp usually works fine for this.

curl --location --request POST ‘https://elmwb.com:9443/jts/oauth-request-token?oauth_consumer_key=92cd70d34ec442329b5651d3d50bfa76&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1645136440&oauth_nonce=HCkegHwV6nx&oauth_signature=RU29jNzYtT%2FLKBOQEjVmfa2FVeg%3D'

As you can see from the above CURL command, I am passing a consumer key of 92cd70d34ec442329b5651d3d50bfa76. My secret was used in the encoding of the signature.   

The response is very simple: 

oauth_token=b87ebfa4fc934690b561720ae6e3af10&oauth_token_secret=B3tvnNvd1xeT1mZ2Fcz0EAQkDI1kmp487lCfGOlrDLM

As you can see I am returned an unauthorized Token and Secret, which I will use in the next step.

Step 2 – Now that we have our unauthorized token, we need to exchange it for an authorized token.  This is our <jfs:oauthUserAuthorizationUrl> API, again from our RootServices document.  We will pass the oauth_token we received in the prior step, to get a authorized token and secret. 

curl --location --request GET 'https://elmwb.com:9443/jts/oauth-authorize?oauth_token=9bef1a97db994466a79930d769cb0f7f'

This will begin a series of redirects (Status code 302) that we will want to follow-until we execute an authorization request from the client application.  This allows for the client to provide the authentication, indicating your credentials are authorized.  Let’s go thru the redirects to understand what is going on.

Key response headers:

X-com-ibm-team-repository-web-auth-msg: authrequired
Location: https://elmwb.com:9443/jts/secure/authenticated/identity?redirectPath=%2Fjts%2Foauth-authorize%3Foauth_token%3Db25d99ad678f47e99daa4d20be21ab63

To process this redirect, I will perform a GET against the value provided by the location header:

curl --location --request GET 'https://elmwb.com:9443/jts/auth/authrequired' \
--header 'Accept: application/rdf+xml’

This will provide a second redirect (Status code 302), along with the following key response header:

Location: https://elmwb.com:9443/jts/auth/authrequired

Once again, we take the location and execute another GET looking for a Status code of 200, and the “X-com-ibm-team-repository-web-auth-msg with the message ‘authrequired’”.

curl --location --request GET 'https://elmwb.com:9443/jts/auth/authrequired'

We can now perform the appropriate user authentication required by our server.  On my ELM server the API end point is j_security_check, this will not be the case if you are using a different server to authenticate your user id.

curl --location --request POST 'https://elmwb.com:9443/rm/j_security_check' \
--header 'oauth_token: 9bef1a97db994466a79930d769cb0f7f' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'j_username=elmadmin' \
--data-urlencode 'j_password=elmadmin'

As you can see the above API, I am passing an encoded form with the user id and password, along with the header, which includes my authorized oauth_token.  Our response should be another redirect (302), with a location header containing the redirect we will follow:

https://elmwb.com:9443/jts/secure/authenticated/identity?redirectPath=%2Fjts%2Foauth-authorize%3Foauth_token%3Dd73245874ae24736ac54b4d99cbe8536

This redirect will provide us with the URL of the Oauth-Authorize function, with the original unauthorized Oauth Request token from Step 1.  We will follow the redirect and related response location header, until we receive a Status code of 200.  On my server this takes two calls.  The final call provides a response explaining what authentication I have:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
        <title>OAuth Consumer Authorization</title>
        <link rel="stylesheet" type="text/css" href="web/com.ibm.team.repository.web/ui/oauth/templates/Authorize.css" />
        <script language="JavaScript" src="web/com.ibm.team.repository.web/ui/oauth/Authorize.js"></script>
    </head>
    <body>
        <div class="boxDiv">
            <div class="content">
                <div clas="header">
                    <h4>You have allowed the server &quot;elmadmin&quot; to collaborate with the server &quot;https://elmwb.com:9443/jts/&quot; using the &quot;elmadmin&quot; user ID.  </h4>
                </div>
            </div>
        </div>
    </body>
</html>

Step 3 – At this point, you call the third API identified in the RootServices document – <jfs:oauthAccessTokenUrl>. Which will provide you with an authenticated token and secret.

curl --location --request GET 'https://elmwb.com:9443/jts/oauth-access-token' \
--header 'Authorization: OAuth oauth_consumer_key=“b20fa15ba1d94a26a62f484b3bf701f9",oauth_token="d73245874ae24736ac54b4d99cbe8536",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1645540874",oauth_nonce="pi6DEJIQwVm",oauth_signature="%2Fyaxha74xspsecKRNWKsOigShSA%3D"

As you can see I am passing my consumer Key and my original token (which has been authenticated).  The response back is simply:

oauth_token=02334c23bc734c48a519ed75a5359f94&oauth_token_secret=moY6vTLGKkUd3PPlmMTb3X0ApgirO9enXjvOSgEVoI

We are now ready to use our Oauth1.0a credentials.  If we want a list of Project Areas on this sever we would simply provide the following:

curl --location --request GET 'https://elmwb.com:9443/rm/process/project-areas?oauth_consumer_key=b20fa15ba1d94a26a62f484b3bf701f9&oauth_token=02334c23bc734c48a519ed75a5359f94&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1645541598&oauth_nonce=oKMVvxXC0LC&oauth_signature=5HUQG2MJzA%2Bk%2FsbfuLdiGhP2mkQ%3D' \
--header 'OSLC-Core-Version: 2.0' \
--header 'Accept: application/json’

Mapping back to our our above steps we see:

  1. We are using our original Consumer Key from the ELM server
  2. We are using our returned Oauth Token from the response of our third API call above.
  3. The oauth signature method, timestamp, nonce and signature are all created as defined above.

If you are familiar with Postman, here are two files to help you process these same steps:

Postman Environment File – OAuth1.0a Demo Environment

Postman API Collection – OAuth1.0a Flow

Wow!  That was a lot of work.  This post took a lot longer than I expected. There has to be an easier way, and there is!   I will cover that in the next post, on how to use OIDC (OpenID Connect – https://openid.net/connect/ ).

Beginning API Discovery (or understanding the RootServices document)


Photo by Felix Mittermeier on Unsplash

Now that we’ve covered some of the basics on what we mean when we say APIs (link to prior post), it’s time to get into the power of ELM thru its API discovery process.

Many of the APIs on a server are exposed thru API calls.  The foundation of this discovery mechanism is the “RootServices” document of the ELM application.  Each ELM application has its own RootServices document. I’ll be focusing on four of the ELM applications, specifically:

  • JTS – Jazz Team Server
  • CCM – Configuration and Change Management – AKA – Engineering Workflow Management
  • QM – Quality Management – AKA – Engineering Test Management
  • RM – Requirements Management – AKA Engineering Requirements Management or DOORS Next

For the purpose of this series of blog posts, I will be using a locally hosted server named https://elmwb.com on port 9443. You will need to replace this server name and port with the server information of your own.

The RootServices API is publicly addressable; it doesn’t require any authentication.  The reason behind this is to ensure that you can discover the services for authentication, and so that servers can query each other regarding what services are available.  Most APIs that are exposed in the RootServices do require authentication.   I will have two separate blog posts which all address authentication, one covering OAuth1.0a and one covering the use of a Bearer token.

RootServices API

The API for accessing the RootServices document is simply:

GET https://<<SERVER>>/<<APPLICATION>>/rootservices

The API also requires you to include at least one of the following headers in order to define the format of the RootServices document:

	accept:application/rdf+xml
	accept:application/json
	accept:text/turtle

If you use CURL the API call would be:

curl --location --request GET ‘https://elmwb.com:9443/rm/rootservices' \
	--header 'accept: application/rdf+xml'

The flexibility of the response body allows you to introspect the contents in an appropriate manner for your specific needs.  One key difference to note in the formats is that both RDF and Turtle formats provide descriptors to simplify the contents; json does not have descriptors.  In RDF, these descriptors are identified with “xmlns” (or xml name space), while in turtle they are identified with @prefix.  Understanding the rest of the differences between these formats is up to you. More information can be found at:

The following images show the beginning of the RootServices document in the above three formats:

application/rdf+xml
application/json
text/turtle

Let’s look at the overall structure and content of the RootServices document and how it will help us for future APIs.  I will be using the rdf+xml format for the rest of this post.

There are three major sections in the RootServices document:

  1. Name Spaces – the name spaces identify the domain and/or process that governs a specific resource.  Each name space is provided with a short identifier which is used as a prefix for defining the full URI which describes a resource.  For example the identifier xmlns:dc describes the “Dublin Core” term as defined at “http://purl.org/dc/terms/“.  This prefix is then used in the definition of resource called <dc:title>.  The full definition of the title can be found at http://purl.org/dc/terms#title .
Dublin Core term example
  • Understanding the details about “Dublin Core” is not critical to the understanding of the RootServices document; it provides a simple way of explaining the name space concept. If you want learn more about “Dublin Core” you can check out their website at https://dublincore.org.
  1. Comments – In the rdf+xml format these lines are identified as <!— —> and are informational only.
  1. Resources – This section describes the name spaces for the server. By placing the service in this RootServices document this can be considered public APIs.  The name space prefix defined earlier in the RootServices document can provide you context about which aspect of the server the specific service addresses.  Some general guidelines include:
  • jd = Jazz Discovery
  • jfs = Jazz Foundation 
  • jdb = Jazz Dashboard
  • ju = Jazz UI elements
  • jp06 = Jazz Process (release 0.6)
  • jp = Jazz Process (release 1.0)
  • oslc = Open Services for Lifecycle Collaboration – this is the Oasis based standard for services across lifecycle based applications.  ELM is both a provider and consumer of OSLC services.  For more information on OSLC and Oasis see: https://open-services.net and https://www.oasis-open.org/ .
  • oslc_XX = OSLC services for a specific application, for example rm = requirements management, cm = change management, etc.
  • rm = Requirements Management, these are DOORS Next specific services
  • trs = Tracked Resource Set version 1.x
  • trs2 = Tracked Resource Set version 2

A detailed (yet somewhat dated) description of the RootServices document can be found on jazz.net at https://jazz.net/wiki/bin/view/Main/RootServicesSpec.

Next, we focus on a few key sections in the RootServices document which will help us discover more APIs as we move forward:

First, after the list of name spaces, we see the Title of the server whose RootServices we are accessing and a detailed description of the server.  They are all in the dc name space, i.e. Dublin Core.

<dc:title xml:lang="en">Doors Next</dc:title>
<dc:description>This application provides the capabilities to create and manage requirements and trace them to modeling, testing, and change and configuration management. You can define, elicit, capture, elaborate, discuss, and review requirements and supporting artifacts.</dc:description>

Second, is a list of common services for the Jazz server and application we are on. Included in this section are those services that are necessary for authentication when using our APIs.

If you are utilizing OAuth1.0a to access the APIs, you should look at the jfs services with include oauth in their name.  On my server, the following services are exposed:

<jfs:oauthDomain>https://elmwb.com:9443/jts,https://elmwb.com:9443/rm</jfs:oauthDomain>
<jfs:oauthRealmName>Jazz</jfs:oauthRealmName>
<jfs:oauthAccessTokenUrl rdf:resource="https://elmwb.com:9443/jts/oauth-access-token" />
<jfs:oauthApprovalModuleUrl rdf:resource="https://elmwb.com:9443/jts/_ajax-modules/com.ibm.team.repository.AuthorizeOAuth" />
<jfs:oauthExpireTokenUrl rdf:resource="https://elmwb.com:9443/jts/oauth-expire-token" />
<jfs:oauthRequestConsumerKeyUrl rdf:resource="https://elmwb.com:9443/jts/oauth-request-consumer" />
<jfs:oauthRequestTokenUrl rdf:resource="https://elmwb.com:9443/jts/oauth-request-token" />
<jfs:oauthUserAuthorizationUrl rdf:resource="https://elmwb.com:9443/jts/oauth-authorize" />

We see three key end points which are necessary for a OAuth1.0a process (which I will cover in detail in another blog post) – oauthAccessTokenUrl, oauthRequestTokenUrl, and oauthUserAuthorizationUrl.  These three URLs are needed to address the three steps that a OAuth authentication that I will cover in another blog post.  

Third, is a list of application specific services.  On our sample server, which is Doors Next application server, we see the following list of services.

<!-- Applications may add any services they provide here -->
<oslc_rm:rmServiceProviders rdf:resource="https://elmwb.com:9443/rm/oslc_rm/catalog" />
    <oslc_rm:majorVersion>7</oslc_rm:majorVersion>
    <oslc_rm:version>7.0.2</oslc_rm:version>
    <oslc_rm:buildVersion>7.0.2</oslc_rm:buildVersion>
    <oslc_config:cmServiceProviders rdf:resource="https://elmwb.com:9443/rm/oslc_config"/>
    <rm:glossaryTermsQuery rdf:resource="https://elmwb.com:9443/rm/glossary/termslookup" />
    <rm:components rdf:resource="https://elmwb.com:9443/rm/cm/component/ldpc" />
    <rm:projectAreas rdf:resource="https://elmwb.com:9443/rm/projects" />
    <rm:cmpProjectAreas rdf:resource="https://elmwb.com:9443/rm/rm-projects" />
    <rm:rrcExtensions>0.1.3-0.1.5</rm:rrcExtensions>
<!-- The admin Web UI service should be uncommented in applications -->
    <jfs:adminWebUI rdf:resource="https://elmwb.com:9443/rm/admin" />
<!-- The registration handler service should be uncommented for application  that do not supply their own -->
    <jd:registration rdf:resource="https://elmwb.com:9443/rm/service/com.ibm.team.repository.service.internal.setup.IRegistrationHandlerService" />
<!--  Process Component Tracked Resource Set 2.0 Provider -->
    <rm:trackedResourceSetProvider>
        <trs2:TrackedResourceSet>
            <trs2:trackedResourceSet rdf:resource="https://elmwb.com:9443/rm/process-trs2" />
            <dc:title>TRS 2.0 for DOORS Next Process Resources</dc:title>
            <dc:description>TRS 2.0 feed for process resources in DOORS Next</dc:description>
            <dc:type     rdf:resource="http://jazz.net/ns/process#" />
            <oslc:domain rdf:resource="http://jazz.net/ns/process#" />
        </trs2:TrackedResourceSet>
    </rm:trackedResourceSetProvider>
<!-- End of application-specific services -->

As you can see this server has OSLC and RM specific services.  We also see that this server is a TRS 2.0 provider. 

Key for our discovery of APIs, we would want to look at the oslc:rmServiceProviders.  A GET to this API will provide us with a catalog of those objects that provide services.  In the case of this server it will be a catalog of the Project areas on our server, along with the services they provide.

Let’s do a quick GET call to the API identified in oslc:rmServiceProviders.  If you are trying this via a browser, you will get prompted for a log in, this tells us that the API needs to be authenticated.  Go ahead and log in and look at the results.  On my server I have setup three Project areas, and it will provide us with the following response:

<rdf:RDF
 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
 xmlns:dcterms="http://purl.org/dc/terms/"
 xmlns:jp="http://jazz.net/xmlns/prod/jazz/process/1.0/"
 xmlns:jazzDisc="http://jazz.net/ns/discovery#"
 xmlns:oslc="http://open-services.net/ns/core#">

 <oslc:ServiceProviderCatalog rdf:about="https://elmwb.com:9443/rm/oslc_rm/catalog">
    <dcterms:title rdf:parseType="Literal">RMCatalog</dcterms:title>
    <dcterms:publisher rdf:resource="https://elmwb.com:9443/rm/application-about" />
    <oslc:domain rdf:resource="http://open-services.net/ns/rm#" />
    <oslc:serviceProvider>
      <oslc:ServiceProvider rdf:about="https://elmwb.com:9443/rm/oslc_rm/_LgdUMIkaEey3TMAARolZjw/services.xml">
         <dcterms:title rdf:parseType="Literal">JKE Banking (Requirements Management)</dcterms:title>
         <jazzDisc:messageReceiver rdf:resource="https://elmwb.com:9443/rm/web/com/ibm/rdm/web/copyReceiver/CopyHandler.html"/>
         <jp:consumerRegistry rdf:resource="https://elmwb.com:9443/rm/process/project-areas/_LgdUMIkaEey3TMAARolZjw/links"/>
         <oslc:details rdf:resource="https://elmwb.com:9443/rm/process/project-areas/_LgdUMIkaEey3TMAARolZjw"/>
      </oslc:ServiceProvider>
    </oslc:serviceProvider>
    <oslc:serviceProvider>
      <oslc:ServiceProvider rdf:about="https://elmwb.com:9443/rm/oslc_rm/_VoWUwIkbEey3TMAARolZjw/services.xml">
         <dcterms:title rdf:parseType="Literal">Sample Project 1 (Requirements)</dcterms:title>
         <jazzDisc:messageReceiver rdf:resource="https://elmwb.com:9443/rm/web/com/ibm/rdm/web/copyReceiver/CopyHandler.html"/>
         <jp:consumerRegistry rdf:resource="https://elmwb.com:9443/rm/process/project-areas/_VoWUwIkbEey3TMAARolZjw/links"/>
         <oslc:details rdf:resource="https://elmwb.com:9443/rm/process/project-areas/_VoWUwIkbEey3TMAARolZjw"/>
       </oslc:ServiceProvider>
    </oslc:serviceProvider>
    <oslc:serviceProvider>
    <oslc:ServiceProvider rdf:about="https://elmwb.com:9443/rm/oslc_rm/_BVnHAImtEeyjbIIiaev_dg/services.xml">
       <dcterms:title rdf:parseType="Literal">Title test</dcterms:title>
       <jazzDisc:messageReceiver rdf:resource="https://elmwb.com:9443/rm/web/com/ibm/rdm/web/copyReceiver/CopyHandler.html"/>
       <jp:consumerRegistry rdf:resource="https://elmwb.com:9443/rm/process/project-areas/_BVnHAImtEeyjbIIiaev_dg/links"/>
       <oslc:details rdf:resource="https://elmwb.com:9443/rm/process/project-areas/_BVnHAImtEeyjbIIiaev_dg"/>
    </oslc:ServiceProvider>
  </oslc:serviceProvider>
</oslc:ServiceProviderCatalog>
</rdf:RDF>

I am not going to go through all the details right now, but just realize we have a list of all the project areas (or Service Providers) on this server.  We can actually execute a GET against a specific project area to get a list of supported services for that Project area.  This can be found in the <oslc:ServiceProvider > section.  But that’s for another blog post.

API types and the API Landing Page

When someone mentions APIs what do you think of?  Let’s look at the acronym and the discuss how I will be using it over the course or this blog series. 

API Blackboard

API stands for “Application Programming Interface”.  This term is pretty broad as it is a connection between computers, programs and software services. APIs can be pubic or private, supported or unsupported.  I will only be talking about Public / supported APIs within the scope of our ELM capabilities.

Accessing a specific API can be done via a SDK (software development kit) or via Rest APIs (Representational state transfer ). I will begin with focusing on Rest API.

IBM ELM API Landing page

Jim Ruehlin & Rosa Naranjo have created a landing page that consolidates all the existing API documentation available across jazz.net (link).  This page provides grouping of existing APIs (both Rest and SDK based APIs), along with how to guides.  One thing to notes is this page references “CLM”.  CLM stands for Collaborative Lifecycle Management ( this is a legacy name for our IBM Engineering Lifecycle Management applications). 

As a developer, we all have an innate definition of what an API is. An API is how we access some function of an application.  APIs are exposed in SDKs (Software Development Kits), compiler or language libraries, and services.  I am going to focus this set of blog posts on service based APIs.  ELM utilizes a standards based Service Oriented Architecture called OSLC.  OSLC or Open Services for Lifecycle Collaboration is specifically designed to address the software development process, utilizing a SOA approach. As such, the API end points and payload requirements are discoverable via services.  I’ll be going thru this discovery process over a few blog posts.

However, let’s start with a quick review of our existing API Landing page and the menu on the right hand side.  As you can see we have sections for basic concept, each of the applications and developing java applications.

OSLC

General OSLC and CLM API information – The foundation for many of our APIs in ELM is OSLC.  OSLC is an open standard for defining messages and APIs across various lifecycle processes.  Getting a good understanding of OSLC is key for many of the topics we will discuss going forward.

Jazz Foundation

Jazz Foundation – Many of the ELM applications are built on top of our Jazz platform.   This foundational component supports a set of common APIs that should be the same regardless of the application you are working with.

Managing Variants

Global Configuration Management – One of the most powerful features of IBM ELM is Global Configuration Management or GCM.  GCM allows you to assemble configurations from across ELM applications.  These Global Configs (GCs) can be reused in different versions or variants of software and product lines. GCM supports the OSLC APIs, a set of Rest API and a Client Extension SDK.

DOORS Next – Requirements management (AKA – DNG) allows for capturing, tracing, analyzing, and managing changes to requirements. There are many different API models that are supported by DNG include OSLC, both client and server SDKs, and Rest APIs for Modules, Reports, ReqIf, and other features.

Rational Team Concert (EWM) – Workflow management allows teams to create and manage work plans across multiple development projects utilizing Agile, SAFe (Scaled Agile Framework for Enterprises), or custom processes. The API’s supported by EWM have a lot of coverage in blogs and other resources, including the work of Ralph Schoon. Ralph has been blogging about EWM APIs for over a decade.  Check out his blog at – https://rsjazz.wordpress.com/. EWM has support for OSLC, REST and SDK based APIs.

Rhapsody Model Manager – Architecture Management allows you to manager your models and code on the same configuration management server.  It also provides linkage between requirements and work items across ELM. It provides a set of REST APIs for accessing artifacts for reporting.

Rational Quality Manager (ETM) – Engineering Test Management enables test planning and management supporting test execution, results reporting, and integration with other testing execution applications.  Supporting both OSLC and REST based APIs, you may also create custom test automation adapters via APIs to integrate with third party test execution tools.

Reporting – ELM reporting is supported both by individual applications and across applications via the ELM Report Builder.  This section provides details on LQE (the Lifecycle Query Engine) and Report Builder, along with Reporting APIs for Requirements (DNG), Test (ETM), and Workflow(EWM).

Plain Java Client or Jazz APIs – The above sections will have links to the appropriate plain java client and other Jazz APIs.  This section provides a single place to get to them regardless of application supported.

If you’ve made it this far, you realize there really is a lot of API documentation and content for you to go thru.  But how can you digest it all?  In the next post I’ll take you thru the common entry point for understanding the APIs supported by a specific ELM application – the RootServices document!

Welcome to API Corner

“With Great Power comes Great Responsibility”. I remember first hearing this phrase in Spider Man comics.  But it has a much longer history than that.  Wikipedia indicates that this is actually an ancient phrase going back to the first century BC.  I find that IBM Engineering Lifecycle Management’s (IBM ELM’s)  incredible flexibility and power is a lot like this phrase.

You have great power in integrating the ELM capabilities with other tools and processes, but you are responsible for understanding a lot of basic concepts.

There’s a lot of existing content available on our API page, but it can be overwhelming if you are not familiar with the applications and some basic concept.  So over the course of this blog series I plan on going thru the major APIs across ELM applications, while also explaining some of the basic concepts behind how to understand our APIs.

Having said all this, let me introduce myself.  My name is Michael Rowe, and I’ve been at IBM since 1995.  In that time I’ve had many roles across development, product management, IBM research, services, and strategy.  In my spare time I develop iOS apps for fun, podcast on gaming and technology, swim and sing light opera.  You can follow those topics over at my personal blog – https://michaelrowe01.com. I joined IBM Rational in 2008 and focused on brand level strategy, looking at how we integrate with other IBM brands (like Tivoli), across the development lifecycle (Urban Code for DevOps, WorkLight for Mobile app development), and with business partners.  I moved to IBM research for a few years to help identify technologies which could and should be brought into products, coming back to the Watson IoT business unit in 2017 to help drive strategy.  I have worked with business partners for a few years and in 2021 came back to my technical roots by joining our ELM Architecture team with a focus on APIs.

I have experienced the same learning curve that many of you will be experiencing as you get started learning how to access and understand our APIs, and as such, we will take this journey together.  My personal approach to learning is to read, in order to understand a concept, and then attempt to validate what I read by performing a simple exercise.  Some of the concepts that we leverage in ELM may require multiple exercises.

My goal is to help you quickly get thru the concepts and find the APIs you need to better leverage the IBM ELM capabilities. Over time, I will be working closely with our product manager, Jim Ruehlin (link – https://jazz.net/wiki/bin/view/Main/JimRuehlin) , to address API documentation and clarity, but more on that in a future post.

I look forward to hearing from you…so let’s get started…

A potential plan for 2022

As I mentioned a while back, I took on a much more technical role in my day job recently. I am working on IBM’s “Engineering Lifecycle Management” product offering as a solution architect. My primary focus is on our reusable components, which includes many of our APIs.

APIs are the building blocks of modern applications, and ELM has had APIs since its foundation many years ago. Many of the APIs are build around the concept of Open-Services for Lifecycle Collaboration or OSLC. The OSLC standard utilizes RDF as the format for its payloads; which is unfamiliar to many people.

RDF allows for a semantically rich representation of data.

Swift Playgrounds

This weekend I wanted to see if I could make a very simple (and quick) iOS application utilizing Swift Playgrounds to read the “root services” document from any ELM application. I had created a iOS app for my own use to do this in the past, utilizing a Swift Package by Gregory Williams called serd-swift. This is a simple wrapper for the fast C library created by David Rollibard – serd.

Of course, like many ideas dreamed up in the shower, this project was not going to be as simple as I wanted. I started by creating a simple iOS app in Swift Playgrounds and adding in the above package. At this point Swift Playgrounds complained that it could not compile “C” code.

The funny thing is that Swift Playgrounds didn’t just come out and say that the Package was the problem, the message showed up as the error – “The target type, “Clang Target”, is not supported. The good news is, I used to write objective C on the Mac, so I knew this meant, the “C” programming Language.

I knew that in advance but had totally forgotten about it. So now my plan is to try to write the original serd library by David into a native Swift Package. This won’t be easy, and I plan to post updates on my blog as I progress.

Stay tuned, as this may take a while.

2022 – Code and Cloud

Well, made it thru to another year. 2022 seems to be starting with the same hope and courage of 2021, let’s hope it ends on a better note.

From Apple’s Xcode Cloud Website

I’ve been working on a Card Tracking app for a few years. It started as a project to hope me learn CoreData, I then pivoted to learn SwiftUI, and finally, I have pivoted again to use it to learn about Xcode Cloud. If you don’t know what that is, I highly recommend you check out the sessions from 2021’s WWDC.

Basically, Xcode Cloud is Apple’s CI/CD service for Xcode. (CI/CD is continuous integration / continuous delivery). This type of tool chain is used in many agile development shops to delivery new functions to customers as quickly as possible. The basic idea is, whenever new code is checked into your source repository the system will build, test, and delivery it as far as you define. In my case I would like to deliver a new build of my card tracking app to testers via TestFlight whenever I deliver new code.

This process is pretty easy, assuming you correctly define your build process. Currently I am able to have the system automatically build both my iOS and macOS apps and then attempt to post them to TestFlight. I say “Attempt” since for some reason the technique I use to auto-increment my builds is not being recognized. Xcode Cloud uses it’s own build number (CI_BUILD_NUMBER), and I can’t find a way to override it with the value I generate from my info.plist. It seems the only I can get this to work is to switch using a new release number; which would then allow for Xcode Cloud to take over the build number.

SwiftUI Fun and Bugs

Well I fixed the bug! The issue was the sequence of the code sequence between the ContactPicker and VStack within the Navigation View. I hadn’t realized that this was issue (Obviously). Here’s a video of the real way it should work.

As you can see the UI is now working as you would expect.

//
//  AddNewRecipientView.swift
//  Card Tracker
//
//  Created by Michael Rowe on 1/1/21.
//  Copyright © 2021 Michael Rowe. All rights reserved.
//

import SwiftUI
import SwiftUIKit
import ContactsUI
import Contacts
import CoreData

/* Thanks to @rlong405 and @nigelgee for initial guidance in getting past the early issues. */

struct AddNewRecipientView: View {
    @Environment(\.managedObjectContext) var moc
    @Environment(\.presentationMode) var presentationMode

    @State private var lastName: String = ""
    @State private var firstName: String = ""
    @State private var addressLine1: String = ""
    @State private var addressLine2: String = ""
    @State private var city: String = ""
    @State private var state: String = ""
    @State private var zip: String = ""
    @State private var country: String = ""

    @State var showPicker = false

    init() {
        let navBarApperance = UINavigationBarAppearance()
        navBarApperance.largeTitleTextAttributes = [
            .foregroundColor: UIColor.systemGreen,
            .font: UIFont(name: "ArialRoundedMTBold", size: 35)!]
        navBarApperance.titleTextAttributes = [
            .foregroundColor: UIColor.systemGreen,
            .font: UIFont(name: "ArialRoundedMTBold", size: 20)!]
        UINavigationBar.appearance().standardAppearance = navBarApperance
        UINavigationBar.appearance().scrollEdgeAppearance = navBarApperance
        UINavigationBar.appearance().compactAppearance = navBarApperance
    }

    var body: some View {
        NavigationView {
            GeometryReader { geomtry in
                ContactPicker(showPicker: $showPicker, onSelectContact: {contact in
                    firstName = contact.givenName
                    lastName = contact.familyName
                    if contact.postalAddresses.count > 0 {
                        if let addressString = (
                            ((contact.postalAddresses[0] as AnyObject).value(forKey: "labelValuePair")
                             as AnyObject).value(forKey: "value"))
                            as? CNPostalAddress {
                            // swiftlint:disable:next line_length
                            let mailAddress = CNPostalAddressFormatter.string(from: addressString, style: .mailingAddress)
                            addressLine1 = "\(addressString.street)"
                            addressLine2 = ""
                            city = "\(addressString.city)"
                            state = "\(addressString.state)"
                            zip = "\(addressString.postalCode)"
                            country = "\(addressString.country)"
                            print("Mail address is \n\(mailAddress)")
                        }
                    } else {
                        addressLine1 = "No Address Provided"
                        addressLine2 = ""
                        city = ""
                        state = ""
                        zip = ""
                        country = ""
                        print("No Address Provided")
                    }
                    self.showPicker.toggle()
                }, onCancel: nil)
                VStack {
                    Text("")
                    HStack {
                        VStack(alignment: .leading) {
                            TextField("First Name", text: $firstName)
                                .customTextField()
                        }
                        VStack(alignment: .leading) {
                            TextField("Last Name", text: $lastName)
                                .customTextField()
                        }
                    }
                    TextField("Address Line 1", text: $addressLine1)
                        .customTextField()
                    TextField("Address Line 2", text: $addressLine2)
                        .customTextField()
                    HStack {
                        TextField("City", text: $city)
                            .customTextField()
                            .frame(width: geomtry.size.width * 0.48)
                        Spacer()
                        TextField("ST", text: $state)
                            .customTextField()
                            .frame(width: geomtry.size.width * 0.18)
                        Spacer()
                        TextField("Zip", text: $zip)
                            .customTextField()
                            .frame(width: geomtry.size.width * 0.28)
                    }
                    TextField("Country", text: $country)
                        .customTextField()
                    Spacer()
                }
            }
            .padding([.leading, .trailing], 10 )
            .navigationTitle("Recipient")
            .navigationBarItems(trailing:
                                    HStack {
                Button(action: {
                    let contactsPermsissions = checkContactsPermissions()
                    if contactsPermsissions == true {
                        self.showPicker.toggle()
                    }
                }, label: {
                    Image(systemName: "person.crop.circle.fill")
                        .font(.largeTitle)
                        .foregroundColor(.green)
                })
                Button(action: {
                    saveRecipient()
                    self.presentationMode.wrappedValue.dismiss()
                }, label: {
                    Image(systemName: "square.and.arrow.down")
                        .font(.largeTitle)
                        .foregroundColor(.green)
                })
                Button(action: {
                    self.presentationMode.wrappedValue.dismiss()
                }, label: {
                    Image(systemName: "chevron.down.circle.fill")
                        .font(.largeTitle)
                        .foregroundColor(.green)
                })
            }
            )
        }
    }

    func saveRecipient() {
        print("Saving...")
        if firstName != "" {
            let recipient = Recipient(context: self.moc)
            recipient.firstName = firstName
            recipient.lastName = lastName
            recipient.addressLine1 = addressLine1.capitalized(with: NSLocale.current)
            recipient.addressLine2 = addressLine2.capitalized(with: NSLocale.current)
            recipient.state = state.uppercased()
            recipient.city = city.capitalized(with: NSLocale.current)
            recipient.zip = zip
            recipient.country = country.capitalized(with: NSLocale.current)
        }
        do {
            try moc.save()
        } catch let error as NSError {
            print("Save error: \(error), \(error.userInfo)")
        }
    }

    func checkContactsPermissions() -> Bool {
        let authStatus = CNContactStore.authorizationStatus(for: .contacts)
        switch authStatus {
        case .restricted:
            print("User cannot grant premission, e.g. parental controls are in force.")
            return false
        case .denied:
            print("User has denided permissions")
            // add a popup to say you have denied permissions
            return false
        case .notDetermined:
            print("you need to request authorization via the API now")
        case .authorized:
            print("already authorized")
        @unknown default:
            print("unknown error")
            return false
        }
        let store = CNContactStore()
        if authStatus == .notDetermined {
            store.requestAccess(for: .contacts) {success, error in
                if !success {
                    print("Not authorized to access contacts. Error = \(String(describing: error))")
                    exit(1)
                }
                print("Authorized")
            }
        }
        return true
    }
}

struct AddNewRecipientView_Previews: PreviewProvider {
    static var previews: some View {
        AddNewRecipientView()
            .environment(\.managedObjectContext, PersistentCloudKitContainer.persistentContainer.viewContext)
    }
}

Update on Holiday Card Tracker

A few years back I started an app that I was thinking of calling Christmas card tracker. The goal of the app was to learn Core Data. I spent time at WWDC working with a fellow attendee to fix problems I was having with Autolayout constraints, and got an early version working enough to where I could start capturing the cards my wife and I send out each year.

I then ran out of time and put it on the back burner. Along comes SwiftUI and I re-wrote the app from scratch, fixing a ton of problems, and along the way making it available for macOS, iPadOS and iOS, I even toyed with a watchOS version. It took me a few years of sporadic work, as I did a rewrite of my app Wasted Time, so that it could run on macOS and tvOS.

Well I am very close to releasing version 1.0, but have run into a very frustrating bug. When I go to add a new recipient of a card I give the user the option of searching their contacts list, or adding a new entry in the app. The Mac version of the screen can be seen here:

Add Recipient Screen.

As you can see, their are three icons. The first is save, the second is search (your contacts) and the the third is close the entry without a save. The problem is, you don’t see a cursor in the first input field – “First Name”. You can use a mouse or the tab key to move the cursor into the field. You can type and tab between fields; however, on the iPad or iPhone, you cannot touch the screen to start entering in the field. You actually can’t enter the cursor into any fields, without a mouse or keyboard.

I am hoping to resolve this issue, but I’ve been banging my head for some time. I’ve tried posting on Stack Overflow, and all I got was a standard (you’ve posted too much code) statement. I posted in two different Swift developer community slacks. And so far I am still stuck. I’ve tried setting the first responder, and that didn’t work. My next idea will be to remove the Geometry Reader that I use to size the City, State, Zip entries.

Will post when I figure this out.

Wasted Time for macOS updated

I just got notified that Wasted Time for macOS has made it thru the App Store review process. That means both the iOS and macOS versions have now been updated to support multiple languages. I am really excited by this and hope to add more languages as I go forward.

Currently, the app supports – German, Brazilian Portuguese, Spanish, Chinese (Hong Kong), Chinese (Simplified) and English. I am still experiencing an upload problem with the tvOS and watchOS versions.. but they will come soon.

App Store rejection

Well, Apple seems to have turned the screws tighter on this build of Wasted Time for the Mac. While I’ve had this out for a while, they noticed that if you closed the main window, there was no way to get it back. So the suggestion is to either add a Cmd-N (for new window) or have it completely terminate the app. I think I will make it terminate the app completely.