Hello! We are MTR Design and site making is our speciality. We work with UK based startups, established businesses, media companies and creative individuals and turn their ideas into reality. This is a thrill and we totally love it.

SAP SuccessFactors SAML Authentication in Python

Author: Hristo Deshev

Working with SAML assertions and OAuth bearer tokens when using the SAP SuccessFactors API

We'll go over the SuccessFactors API authentication mechanism and how to make it play nice with Python. We will be using SAML assertions in our OAuth authentication and then use the OData API to read SuccessFactors entities. Fully-working source code and sample scripts included. read more ›

The SuccessFactors API gives us access to any data entity in the system with an easy to use interface. And I really mean easy -- the coolest thing about it is that it is based on the OData standard. OData is both simple to use in ad-hoc requests and there are a lot of client libraries out there that can make building queries easier. I usually prefer the former approach.

The OData specification does not specify an authentication and authorization mechanism, and the SuccessFactors team has decided to embrace another popular standard: OAuth 2.0 using the SAML bearer assertion flow. The SFSF SAML authentication story is not too different than the Jam one, but it has its own quirks. In a way it could be a lot easier to use SAML assertions with SuccessFactors, but that comes with a price - there is an associated security risk that I will help you avoid by doing some extra work.

The plan for this guide:

  • Configuring SuccessFactors for OAuth authentication.
  • Generating SAML assertions: ** Using the SFSF API. ** Using our own XML-signing code.
  • Obtaining an access token.
  • Authenticating OData requests.

OAuth Access Configuration

Unlike Jam, when working with SuccessFactors authentication, all you need to do is configure an OAuth client application. SFSF is smart enough to use it as an identity provider if you configure an X.509 certificate for your application.

Registering the application goes as following:

  • Generate a RSA key pair and export your public key as a X.509 certificate. Use the generate_keys.sh tool and consult the Jam SAML article if you get stuck.
  • Go to the SFSF AdminTools page and add a new OAuth client. Paste your X.509 certificate body in the textbox:

OAuth settings

 

Note: OpenSSL-generated certificates contain -----BEGIN CERTIFICATE-----/-----END CERTIFICATE----- text guards in their first and last lines respectively. The SuccessFactors admin seems to choke on those, so you need to remove the first and last lines. Just select the certificate body between those lines and paste it in the textbox above.

Generating the SAML assertion

There are two ways you can generate a SuccessFactors SAML assertion:

  • By using the SFSF assertion API.
  • By generating it and signing it yourself.

I'd like to take the moment and give you a warning against using the assertion API in production. First, there is the performance side of the story -- there is an extra server roundtrip involved every time you authenticate against the server which can get slow.

Even more important here are the security implications. In order to generate and sign a SAML assertion, the server needs access to your private key. Read that again -- you will be giving out your private key to someone else. That doesn't sit too well with me. I'd recommend that you try the API a couple of times to get a hold on the generated assertion document and then start generating that yourself.

Using the SuccessFactors Assertion API

With the above warning in place, let's get started talking to the assertion API. We need to issue a HTTP POST request to the /oauth/idp resource and pass the OAuth client id, our authenticated user ID, the access token generation URL (used as the next authentication step), and our specially formatted private key.

def get_assertion_from_sf(self):
    """
    Send our private key to the SFSF IdP API and let it generate an assertion for us.
    Not ideal and incurs an additional API roundtrip. Use only for testing/debugging purposes.
    """
    user_id = self.sf_user_id
    with open(self.private_key) as key_file:
        # remove ---BEGIN/---END lines (first and last)
        # strip whitespace and squash everything on a single line
        flattened_key = ''.join([l.strip() for l in key_file.readlines()[1:-1]])

    assertion_request = dict(
        client_id=self.oauth_client_id,
        user_id=user_id,
        token_url=self.odata_url,
        private_key=flattened_key,
    )
    response = requests.post(self.idp_url, data=assertion_request)
    response.raise_for_status()
    return response.content

Note the key "flattening" logic above. We need to get rid of our first and last lines (containing the -----BEGIN RSA PRIVATE KEY-----/-----END RSA PRIVATE KEY----- markers), strip whitespace and squash everything in a single line.

The assertion we get back is a single-line base64-encoded XML document that we can just pass to the access code API (see below).

Generating an Assertion Ourselves

A quick base64-decode on the SAML document we get from the API above can show us how we can generate such a document ourselves. Here are the data items we need:

  • The recipient URL -- set to the SFSF OData URL.
  • The SAML audience string -- hardcoded to www.successfactors.com.
  • The authenticated user ID.
  • The OAuth application client ID.
  • A session id that doesn't matter too much. We hardcode it to mocksession.
  • Some timestamps: authentication instant and expiration times.

And now the XML template:

<saml2:Assertion
IssueInstant="{issue_instant}" Version="2.0"
xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <saml2:Issuer>{client_id}</saml2:Issuer>
  <saml2:Subject>
    <saml2:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified">{user_id}</saml2:NameID>
    <saml2:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">

      <saml2:SubjectConfirmationData NotOnOrAfter="{not_valid_after}"
      Recipient="{sf_root_url}/odata/v2" />
    </saml2:SubjectConfirmation>
  </saml2:Subject>
  <saml2:Conditions NotBefore="{not_valid_before}"
  NotOnOrAfter="{not_valid_after}">
    <saml2:AudienceRestriction>
      <saml2:Audience>{audience}</saml2:Audience>
    </saml2:AudienceRestriction>
  </saml2:Conditions>
  <saml2:AuthnStatement AuthnInstant="{issue_instant}"
  SessionIndex="{session_id}">
    <saml2:AuthnContext>
      <saml2:AuthnContextClassRef>
      urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml2:AuthnContextClassRef>
    </saml2:AuthnContext>
  </saml2:AuthnStatement>
  <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
    <SignedInfo>
      <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315" />
      <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
      <Reference URI="">
        <Transforms>
          <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
        </Transforms>
        <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
        <DigestValue></DigestValue>
      </Reference>
    </SignedInfo>
    <SignatureValue/>
  </Signature>
</saml2:Assertion>

We can now use an approach similar to the one used when generating and signing Jam assertions:

def get_local_assertion(self):
    """
    Generate and sign the SAML assertion ourselves.
    """
    user_id = self.sf_user_id

    unsigned_assertion = sf_saml.generate_assertion(
        sf_root_url=self.server_url,
        user_id=user_id,
        client_id=self.oauth_client_id
    )
    signed = sf_saml.sign_assertion(unsigned_assertion, self.private_key)
    return signed.encode('base64').replace('\n', '')

The sf_saml module takes care of generating and signing assertions and is very similar to the code we used to handle Jam assertions.

Note that at the end of the function above we still need to base64-encode our signed XML and squash it into a single line.

Obtaining an Access Token

Armed with our assertion, we can now ask for an access token using a HTTP POST request against/oauth/token. The only thing worth mentioning here is that we need to pass a grant_typeparameter of urn:ietf:params:oauth:grant-type:saml2-bearer and include our OAuth client ID, our company ID and the assertion as well.

def get_access_token(self, assertion=None):
    if not assertion:
        assertion = self.get_local_assertion()

    token_request = dict(
        client_id=self.oauth_client_id,
        company_id=self.company_id,
        grant_type='urn:ietf:params:oauth:grant-type:saml2-bearer',
        assertion=assertion
    )
    response = requests.post(self.access_token_url, data=token_request)
    token_data = response.json()
    return (token_data['access_token'], token_data['expires_in'])

Token Authentication for OData Requests

Having gotten an access token, we can now issue OData requests. All we need is to pass the token via the Authorization HTTP header:

headers["authorization"] = 'Bearer {}'.format(self.access_token)

A minor detail above that needs mentioning: the token needs to be prefixed with the Bearer string to indicate its type.

We can now pack everything together in a single SFSession class that wraps the "requests"get/post API and calls our SFSF server. Here is an example that fetches our user details:

SF_URL = os.getenv('SF_URL')
SF_SAML_PRIVATE_KEY = 'sf-private.pem'
SF_USER = sys.argv[1]
SF_COMPANY_ID = os.getenv('SF_COMPANY_ID')
SF_OAUTH_CLIENT_ID = os.getenv('SF_OAUTH_CLIENT_ID')
SF_OAUTH_CLIENT_SECRET = os.getenv('SF_OAUTH_CLIENT_SECRET')

session = SFSession(server_url=SF_URL,
                    private_key=SF_SAML_PRIVATE_KEY,
                    company_id=SF_COMPANY_ID,
                    oauth_client_id=SF_OAUTH_CLIENT_ID,
                    sf_user_id=SF_USER)
response = session.get("/odata/v2/User?$filter=userId eq '{}'".format(SF_USER))

The OData query is so simple, we don't even need to take care of much URL escapes most of the time. I really like that protocol.

Running the code above we get a JSON document:

{u'd': {u'results': [{u'__metadata': {u'type': u'SFOData.User',
                      ...
                      u'addressLine1': u'1500 Fashion Island Blvd',
                      u'city': u'San Mateo',
                      u'country': u'United States',
                      u'custom10': u'admin',
                      u'dateOfCurrentPosition': u'/Date(983404800000)/',
                      u'dateOfPosition': u'/Date(1388534400000)/',
                      u'defaultLocale': u'en_US',
                      u'department': u'Industries (IND)',
                      u'division': u'Industries (IND)',
                      u'email': u'admin@ACEcompany.com',
                      u'firstName': u'Emily',
                      u'lastName': u'Clark',
                      ...
                      u'zipCode': u'94404'}]}}

Source Code

The full source code is available on GitHub.

New files of interest in the sample project dir:

  • sf_saml.py generates and signs SAML assertions.
  • sap_sf.py authenticates and makes requests to the Jam API.
  • get_sf_user.py makes a sample API call that retrieves member details.

SAP Jam SAML Authentication Using Python

Author: Hristo Deshev

A quick introduction to authenticating to the SAP Jam API using SAML assertions.

We'll show how to generate and sign assertion XML documents, then use them to authenticate against the SAP Jam API using OAuth SAML bearer tokens. All that packaged in a small sample project with fully-working source code. read more ›

One of the most exciting things in our projects is working with new API's. This time it's the SAP Jam API. It has pretty good Java support, but I wanted to use it from our Python codebase, so I had to get creative.

Calling the SAP Jam API from Python is not too complex, but there are a few places that can get tricky. I managed to put the pieces together from several sources, so this is my attempt to document most if it:

  • Generating keys and registering them with SAP Jam.
  • Generating SAML assertion documents.
  • Signing the above as a SAML identity provider would.
  • Submitting an assertion to the server and getting back an OAuth SAML bearer token.
  • Authenticating API calls using the SAML bearer token.

Generating Keys

We are using 2048-bit RSA keys, generated with openssl:

# generate private key
openssl genrsa -out jam-private.pem 2048

# export public X509 certificate
openssl req -new -x509 -key jam-private.pem -out jam-public.cer -days 3650

Alternatively you can just run the generate_keys.sh script in my sample project (see below). The thing to note is that you generate keys once and keep the files. You will need them when registering your OAuth application and calling the API.

OAuth Access Configuration

We'll do this in the "Jam Admin" area. We need two pieces: an OAuth application and a trusted SAML identity provider.

OAuth Client Application

The OAuth Application is pretty straightforward. Register your domain and application URLs and do not set up an X509 certificate.

OAuth registration

Then we register a SAML Identity Provider (IdP). Note the IDP ID, Allowed Assertion Scope and X509 certificate fields:

SAML IdP configuration

SAML assertions

According to Wikipedia, the Security Assertion Markup Language (SAML) is an XML-based standard that lets different services handle authentication and authorization together. It is typically used to implement single sign-on (SSO) scenarios.

To use SAML with SAP Jam, you need to generate an assertion XML document describing the user you want to impersonate, yourself as the issuer, and some extra pieces of data such as validity periods. Now the full list:

  • Issuer. Typically a domain name such as example.com. You must use the one you provided when you registered the trusted IdP in the Jam admin area.
  • Subject. This is your user. SAML defines many ways to specify users, some allowing apps to use temporary opaque user ID's. We'll use the urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress name ID format and just pass the user email address.
  • Validity. We set up the proper SubjectConfirmationData, AuthStatement, and Conditions element with the correct authentication timestamp and and NotBefore and NotOnOrAfter points in time.
  • OAuth client ID. We pass our OAuth application's client ID.
  • Audience. Hardcoded to cubetree.com.

Here is how our full XML generation template looks like:

<Assertion xmlns="urn:oasis:names:tc:SAML:2.0:assertion"
xmlns:ns2="http://www.w3.org/2000/09/xmldsig#"
xmlns:ns3="http://www.w3.org/2001/04/xmlenc#" ID="bo.ilic.test.idp"
IssueInstant="{issue_instant}" Version="2.0">
  <Issuer>{issuer}</Issuer>
  <Subject>
    <NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">{user_id}</NameID>
    <SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">

      <SubjectConfirmationData NotOnOrAfter="{not_valid_after}"
        Recipient="{jam_root_url}/api/v1/auth/token" />
    </SubjectConfirmation>
  </Subject>
  <Conditions NotBefore="{not_valid_before}"
  NotOnOrAfter="2014-04-15T14:36:22.235Z">
    <AudienceRestriction>
      <Audience>{audience}</Audience>
    </AudienceRestriction>
  </Conditions>
  <AuthnStatement AuthnInstant="{auth_instant}"
  SessionIndex="mock_session_index">
    <AuthnContext>
      <AuthnContextClassRef>
      urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</AuthnContextClassRef>
    </AuthnContext>
  </AuthnStatement>
  <AttributeStatement>
    <Attribute Name="client_id">
      <AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:type="xs:string">{client_id}</AttributeValue>
    </Attribute>
  </AttributeStatement>
  <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
    <SignedInfo>
      <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315" />
      <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
      <Reference URI="">
        <Transforms>
          <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
        </Transforms>
        <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
        <DigestValue></DigestValue>
      </Reference>
    </SignedInfo>
    <SignatureValue/>
  </Signature>
</Assertion>

Signing Assertions

You might have already noticed the <Signature> element in the assertion document above, and most importantly its <SignatureValue> child node. This is, well, where our signature has to go.

Aside: Signing XML Documents

Signing plain text messages is easy - the message content has a single representation that can be used to compute the signature. Unfortunately, that is not the case with XML. For example, the two documents below look different, yet are completely equivalent:

<user name="John" email="john@example.com" />

and

<user
    email="john@example.com" 
    name="John"></user>

To solve the multiple valid representations problem we need a way to normalize or canonicalize XML documents that will guarantee that, when applied to the two documents above, will yield the same results for both of them. That will make it possible for us to sign XML documents and verify signatures. Of course, people have come up with such algorithms already. For details, check Wikipedia's article on Canonical XML.

Signing XML in Python: xmlsec to the Rescue.

Implementing XML canonicalization isn't a simple task, but, well, this is Python and most of the time people have already solved problems like that before. We will be getting the xmlsec package off PyPI and using it to sign our assertions. It turns out to be quite easy, so I'll just give you the code:

def sign_assertion(xml_string, private_key):
    root = etree.fromstring(xml_string)

    signature_node = xmlsec.tree.find_node(root, xmlsec.Node.SIGNATURE)
    key = xmlsec.Key.from_file(private_key, xmlsec.KeyFormat.PEM)

    sign_context = xmlsec.SignatureContext()
    sign_context.key = key
    sign_context.sign(signature_node)

    return etree.tostring(root)

Note that the code above assumes your XML string already contains a <Signature> node. Just like our XML template had above.

Obtaining SAML Bearer Tokens

Now that we have our assertion nicely signed, we need to pass it to the Jam server. We do that by base64-encoding the assertion document and getting rid of all whitespace, so that everything fits on a single line. We then issue a HTTP POST request to /api/v1/auth/token:

def request_token(self, assertion):
    encoded_assertion = re.sub(r'\s', '', assertion.encode('base64'))
    post_params = dict(
        client_id=self.client_id,
        client_secret=self.client_secret,
        grant_type="urn:ietf:params:oauth:grant-type:saml2-bearer",
        assertion=encoded_assertion,
    )

    token_url = self.url_for("/api/v1/auth/token")
    response = requests.post(token_url, data=post_params)
    response.raise_for_status()
    return response.json()['access_token']

Note the saml2-bearer grant type above and the client_id and client_secret values. Again, you'll get the last two from your registered OAuth application settings in the Jam admin. Just have in mind that Jam calls the client_id value just key:

Jam OAuth client settings

Token Authentication

Once you've gotten hold of the token, you can issue API requests, by passing the token in an Authorization header. Here's how to do it:

headers["authorization"] = 'OAuth {}'.format(self.access_token)

Note the mandatory 'OAuth' prefix! And don't mind the lowercase "authorization" key -- HTTP headers are not case-sensitive.

Wrapping assertion generation, signing, and obtaining tokens in a simple JamSession class, we can now get our profile details, by issuing a HTTP GET request for /api/v1/members:

session = JamSession(server_url=JAM_URL,
                     issuer=JAM_IDP_DOMAIN,
                     private_key=JAM_SAML_PRIVATE_KEY,
                     client_id=JAM_OAUTH_CLIENT_ID,
                     client_secret=JAM_OAUTH_CLIENT_SECRET,
                     jam_access_email=JAM_EMAIL)
response = session.get('/api/v1/members')
pprint(response.json())

And here's the result we get back:

{u'assistant_ids': [],
 u'company-name': u'Ace',
 u'country_code': u'United States',
 u'created-at': 1378332130,
 u'current-status': {u'created-at': 1403259039,
                     u'id': 4452,
                     u'member-id': 98390,
                     u'source': u'Web',
                     u'status': u'<a href="dsasda">dsa</a>',
                     u'updated-at': 1403259039},
 u'direct_report_ids': [],
 u'email-addresses': [{u'address': u'admin@example.com',
                       u'location': u'Primary'}],
 u'first-name': u'Admin',
 u'handle': u'admin',
 u'id': 98390,
...
}

Full Source Code

I packaged the entire project and put it up on GitHub. Things of interest in there:

  • requirements.txt to set up your virtualenv.
  • generate_keys.sh to (duh!) generate RSA keys.
  • jam_saml.py generates and signs SAML assertions.
  • sap_jam.py authenticates and makes requests to the Jam API. You'll find the JamSession class you've seen above here.
  • get_jam_member.py makes a sample API call that retrieves member details.

Dizzyjam @ Music Hack Day

Author: Emil Filipov

If you had a slumberous February weekend, there is no reason to feel bad about it - after all, most of the world did. There was a special group of people, however, who gave up sleep and rest in favor of creating awesome applications that could change the way you and I experience music. Yes, I'm talking about the hackers that took part in the Music Hack Day event in San Francisco. read more ›

If you had a slumberous February weekend, there is no reason to feel bad about it - after all, most of the world did. There was a special group of people, however, who gave up sleep and rest, in favor of creating awesome applications that could change the way you and I experience music. Yes, I'm talking about the hackers that took part in the MusicHackDay event in San Francisco. These are the guys pushing the envelope, and these are the ideas you should watch out for, in case you have anything to do with the music industry.

The event produced 66 projects ranging from turning body outlines to soundwaves via a Kinect controller to a web platform for borrowing/renting musical instruments. It's an (yet) invisible creativity explosion - the sort of mini-nova that bursts into billions particles, giving birth to planets and star systems, millions of years later. Well, in the IT gravitational field a million of years pass just like one day, so we should expect the results quite soon! 

Thanks to the organizers, we were able to do an online presentation of the Dizzyjam website, and more specifically, of a new feature we've recently added - the Dizzyjam API. As you might expect, it s a web-based, RESTful API that enables you to access all Dizzyjam functions programmatically. It boasts a web console built into the docs, a WordPress plugin, bindings for Python and PHP, as well as a piece of unique functionality - creating new Dizzyjam users through your API account (see the manage/create_user method). The API got utilized by a very interesting project during the hackathlon - Merchr. It's the why-did-not-I-think-of-it-first kind of project - simple idea that could be a game changer one day. I sincerely hope that the guys behind this project will keep on hacking and bringing good stuff out!

Published in: Development, Projects

Get in business with Cotton Cart

Author: Milen Nedev

The new merchandising platform makes it possible for all of you to make money from your design. Show us your style! read more ›

Cotton Cart, our newest project, has just launched.

Some of you are probably already familiar with Dizzyjam - our e-commerce and merchandising platform which we created to make it easy and risk-free for anyone in the music industry to make money from their merchandise.

In the past we’ve received quite a lot of requests from people who wanted to use Dizzyjam for trading non-music stuff. And as those requests grew we started thinking about including a non-music section in the original website. Or creating an entirely new website for those who want to sell merchandise no matter what their business activity is. After short reflection we went for the second option and just before Christmas we did a soft launch of Cotton Cart.

The new site follows the overall idea of www.dizzyjam.com – in only three simple steps anyone can start making money - upload your designs, create your products and start selling. You don’t have to buy 100 blank t-shirts, to organize printing or pile up all the stuff you can’t sell. It won’t cost you a penny. But it will cost you creativity and popularity in order to make anyone besides your granny buy your stuff. Cotton Cart is here to solve the popularity issue.

Who can use this website?

Everyone. This may be a graffitist who wants to get famous, the grocery shop around the corner, where the best veggies are sold or a charity organisation that raises money for its cause. In fact such fundraising activities were the first to open their virtual stalls in Cotton Cart. Another clever idea is to use the platform for producing t-shirts or other merch for corporate events – team buildings, annual meetings and seminars. The website can be used for promoting sports events – just upload your local rugby team’s design, print your merch and sell them to your fans in the neighbourhood. Surely you will have an audience to remember the next time your team meets the rivals.

The possibilities are countless – your imagination is the limit. So far we have charity and fundraising groups, festivals, sports events and we can’t wait to see what else you can think of while using Cotton Cart.

Python and Django from dawn till dusk

Author: Emil Filipov

Want to learn Python and Django? Then this free seminar is just for you! Ninja training starts at the Telerik Academy in Sofia on Feb 3rd, 2013. read more ›

We've been invited to do another training session on Python and Django at the Telerik Academy. This time, it will be an intensive morning-to-evening seminar, with the aim of getting you from zero to hero on both Python and Django. Well, maybe not a true hero, but it will give you the basics of both technologies, so you can go on and study/work with them on your own. If you're in Sofia and getting into Python or Django has always been an unfulfilled childhood dream for you, or if you simply want to pick up some new and highly competitive skills for free, then waste not another minute - hurry to http://academy.telerik.com/seminars/python-and-django-development and reserve your seat!

Published in: Company News, Development