Integrate Atlassian Cloud with ForgeRock Identity Cloud

Introduction

This article describes the steps necessary to integrate Atlassian Cloud (Atlassian) with ForgeRock Identity Cloud (ID Cloud) to achieve the following outcomes:

  • Users can access Atlassian applications using their ID Cloud account
  • SSO experience when accessing Atlassian and other services integrated with ID Cloud
  • Centralised management of user profiles and Atlassian access rights in ID Cloud

:warning:Note:

  • Both cloud services are updated on a regular basis and the instructions provided here may differ from the current version
  • The integration can also be achieved using a ForgeRock self-managed or on-premises deployment, however the instructions here are based on ID Cloud

What you will need

The following components are required and require administrative access to perform the configuration steps:

Integration Approach

The integration consists of two interfaces:

  • SAML v2 : used by Atlassian redirect authentication of users to ID Cloud. Atlassian does not support SLO so SAML is used exclusively to verify user credentials during sign in.
  • SCIM : In contrast to other services that offer JIT (Just in time) provisioning of users during sign in, Atlassian relies on the SCIM protocol. This is used to manage both user and group information in Atlassian

image8

Configure SSO

Configure ForgeRock IDP

Skip this section if you already have an existing SAML IDP configured in your tenant.

Create a Circle of Trust:

  1. Goto the Identity Cloud admin UI page and sign in
  2. Open the Access Management from the Native Consoles menu
  3. Select Applications > Federation > Circles of Trust
  4. Select the Add Circle of Trust button
  5. Enter a name and select the Create button

Create a Hosted IDP:

  1. Select Applications > Federation > Entity Providers
  2. Select Add Entity Provider and choose Hosted
  3. Enter a unique value in **Entity ID. You can **use the same value of the Entity Provider Base URL which defaults to the AM URL of your tenant
  4. Enter a value in Identity Provider, for e.g. idp
  5. Select the name of the CoT you created from the Circles of Trust list box
  6. Select the Create button

Prepare ForgeRock IDP Metadata

Follow these steps to prepare the information necessary to configure your IDP in Atlassian.

Export the IDP metadata:

  1. Goto the Identity Cloud admin UI page and sign in
  2. Open the following URL replacing the text in brackets with your own values

https://[tenant-env-fqdn]/am/saml2/jsp/exportmetadata.jsp?entityid=[entityID]&realm=/[realmname]

Example:

https://openam-example-demo.forgeblocks.com/am/saml2/jsp/exportmetadata.jsp?entityid=https://openam-example-demo.forgeblocks.com/am&realm=/alpha

For the Identity provider Entity ID in Atlassian use the value of the entityID attribute in the following element:

<EntityDescriptor . . . entityID="https://openam-example-demo.forgeblocks.com/am">

The Location attribute in the following element corresponds to the Identity provider SSO URL:

<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://openam-example-demo.forgeblocks.com/am/SSOPOST/metaAlias/alpha/idp"/>

Save the value of the ds:X509Certificate element to a file cert.der:

<KeyDescriptor use="signing">
<ds:KeyInfo>
<ds:X509Data>
<ds:X509Certificate>signing_certificate_data<\ds:X509Certificate>

Convert the** **file with certificate data you saved in the previous step into the format required by Atlassian configuration as Public x509 certificate:

#!/bin/bash
# convert certificate from base64 encoded der to pem
base64 -D < cert.der | openssl x509 -inform der -outform pem -out cert.pem

Verify Domain in Atlassian

Verify the domain(s) of the user’s who access Atlassian using ID Cloud:

  1. Goto the Atlassian Administration URL of your tenant and sign in
  2. Select Settings from the top level navigation bar
  3. Select Domains on the left navigation bar
  4. Choose DNS or HTTPS as the verification method, depending on whether you have access to the DNS records or web server for the domain
  5. Follow the instructions for the verification method you chose
  6. Select the Verify domain button
  7. When the status of the domain listed under Domains is VERIFIED proceed to the next step

Configure Atlassian SSO and User Provisioning

The steps required are based on the instructions for 3rd party IDP integration from the Atlassian online documentation Connecting to a SAML identity provider for single sign-on | Hipchat Data Center and Server Server | Atlassian Documentation.

  1. Goto the Atlassian Administration URL of your tenant and sign in
  2. Select Security from the top level navigation bar
  3. Select Identity providers on the left navigation bar
  4. Choose Other provider from the list of identity providers
  5. Enter a descriptive value in Directory name, for e.g. ForgeRock
  6. Select the Add button
  7. On the next screen select **Set up SAML single sign-on **to start the configuration wizard
  8. Select the Next button
  9. Enter Identity provider Entity ID using the value of the entityID element from the IDP metadata, for e.g. _https://openam-example-demo.forgeblocks.com/am_
  10. Enter Identity provider SSO URL using the value of the Location attribute from the metadata element
  11. Enter Public x509 certificate using the contents of the cert.pem file you prepared in the previous section
  12. Select the Next button
  13. Make a note of both URLs - they will be required when configuring Atlassian as an service provider in ForgeRock
  14. Select the Next button
  15. Choose the domain that you verified from the list
  16. Select the Next button
  17. Select the Confirm move button
  18. Select the Set up provisioning button
  19. Make a note of SCIM Base URL and API Key for configuration in ForgeRock
  20. Select the Next button and save the configuration

Configure Atlassian Authentication Policy

  1. Goto the Atlassian Administration URL of your tenant and sign in
  2. Select Security from the top level navigation bar
  3. Select Authentication policies on the left navigation bar
  4. Select the Edit button next to the directory you set up previously which should display Single sign-on as disabled
  5. Select the Enforce single sign-on option
  6. Select Update and confirm changes

Configure Atlassian SP in ForgeRock

Prepare the metadata for importing into ForgeRock by saving the following code block and replacing the values of the following attributes with values from the SAML configuration in Atlassian :

  • sp_entity_url with the value of Service provider entity URL
  • sp_assertion_consumer_url with the value of Service provider assertion consumer service URL
<?xml version="1.0"?>
<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata"
    entityID="service_provider_entity_url">
    <md:SPSSODescriptor AuthnRequestsSigned="false" WantAssertionsSigned="false" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
        <md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat>
        <md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
            Location="sp_assertion_consumer_url"
            index="1" />
    </md:SPSSODescriptor>
</md:EntityDescriptor>

Note:

The NameIDFormat element is set to emailAddress as Atlassian expects an email address to be returned and does not specify it in the authentication request.

Testing SSO

Testing SSO without user provisioning requires a valid user account in ForgeRock with an email address containing the domain you registered in Atlassian. Use a private browser window to ensure that you have no existing session in ForgeRock or Atlassian.

Example:

Sign in to your Atlassian tenant and enter an email address with a domain you verified in the Atlassian SSO configuration:

image2image4

You should then be redirected to ForgeRock and prompted to sign in:

image5

When the login journey is completed successfully you should be redirected back to Atlassian. If no user profile information has been provisioned via SCIM and your user account does not exist yet in Atlassian you will be prompted to request access:

image1

Open another browser window and goto the Atlassian Administration URL of your tenant.

Sign in and Select Directory, Users.

Enter the email address of the user and select Add team members. The user will be added to the list of users displayed at the bottom of the screen and shown as Invited:

image3

Return to the browser with the user session and refresh. You should be prompted to answer or skip several questions:

image6

And presented with the Atlassian dashboard:

image7

Configure User Provisioning in ForgeRock

Configure SCIM Connector

Configure a SCIM connector using instructions Create a connector configuration over REST :: ForgeRock Identity Cloud Docs and modify the following properties before sending the configuration to ID Cloud using Postman:

  • authenticationMethod with TOKEN
  • SCIMEndpoint with the value of SCIM Base URL
  • authToken with the value of API Key
//excerpt of SCIM connector configuration
    "configurationProperties" : {
        "authenticationMethod" : "TOKEN",
        "SCIMEndpoint" : "scim_base_url",
        "authToken" : "api_key",
        "acceptSelfSignedCertificates" : false,
        "clientId" : null,
        "clientSecret" : null,
        "disableHostNameVerifier" : false,
        "httpProxyHost" : null,
        "httpProxyPort" : null,
        "maximumConnections" : "10",
        "password" : null,
        "SCIMVersion" : "2",
        "tokenEndpoint" : null,
        "user" : null
    }

Map User Data to SCIM Format

Create a mapping from Managed User to the connector created in the previous step. In minimum the mapping must include the following attributes:

Source Target
mail userName
mail emails

Add the following transformation to script to the mail attribute which outputs a multi-value attribute as defined by the SCIM schema for the emails attribute. Note that other attributes such as phoneNumbers require similar transformation if used in the mapping.

/* 
 * IDM transform script
 * 
 * Converts mail attribute to a multi-value attribute
 *  defined by RFC 7643: SCIM Core Schema 
 * 
 */
function convertMailToScimFormat() {
	var mails = [];
  	var obj = new Object();
  	obj.value = source;
  	obj.type = "work";
  	obj.primary = true;
  	mails.push(obj);
  	return mails;
}

convertMailToScimFormat();

To enable or disable Atlassian accounts based on the status of the user object in ID Cloud map the accountStatus attribute to the active attribute and add the following transformation script:

/* 
 * IDM transform script
 * 
 * Converts "accountStatus" attribute to a boolean value for the "active" attribute
 *  defined by RFC 7643: SCIM Core Schema 
 * 
 */
if (source == "active") {
  active = true;
}
else {
  active = false;
}
active;

The attributes mapping should look like this:
mapping-attributes-grid

In this example the preview is showing two users where the value of accountStatus is “active” and one user with the value “inactive”:
map-source-preview

After selecting Reconcile users with an email that matches the domain that you registered in Atlassian will be synchronized and listed in the Atlassian Administration interface under Directory, Users. Note the Status of the user atlassian3@poc… is set to Deactivated:

atlassian-users

1 Like

SCIM version should be “2”. SCIM v1 doesn’t appear to be working anymore.

1 Like

@ioandamian many thanks for pointing that out! I’ve updated the article accordingly. Any other feedback is most welcome.

1 Like

Hi @nick.hangartner

The script that Converts “accountStatus” attribute to a boolean value for the “active” attribute fails with the following error

Converts "accountStatus" attribute to a boolean value for the "active" attribute

I’ve modified and added source.accountStatus and no longer return the error, however the conversion doesn’t happen:

if (source.accountStatus == "active") {
  active = true;
}
else {
  active = false;
}
active;

Can you please look into this and advise?
Thanks!
Vlad

Hi @vladpanainte
The modification to the script was probably required because the source attribute wasn’t specified. Refer to the screenshot I’ve now included showing the attribute map.
I’m not sure what you mean by the conversion doesn’t happen. I’ve shown a working example of users being synchronized with the correct status.

Thanks @nick.hangartner for the reply. Please see attached screenshots.

If I used the script with source.accontStatus works but after reconcile the status active is not changing on Jira. Please disregard the statement about conversion doesn’t happen.

Thanks!
Vlad

I can only attach one image per comment.

Please look closely on the mappings in the screenshot provided. The transformation script set “active” - true / false based on the accounStatus . When accoutnStatus is “active” active become - true which is correct but when accountStatus is “inactive” the active become-“inactive” string not boolean false. Does it make sense now? If this is not clear enough, please let me know.

This is really interesting. I’m not sure exactly what is happening here, but what I am suspecting is that if your return value from the script is a boolean value of false the transformation is simply not being applied. I tried this out myself and was able to replicate the issue. The only way I was able to get a value of false out of the transformation was to use a string.

This is indeed strange since the target is of boolean type. Can it be a javascript issue, e.g the active variable is not declared in the script (e.g I would expect something like var active = true for example).

Does this have the same behaviour?

if (source == "active") {
    return true;
}
return false;

That code has it’s own issue, in that you can’t actually use the return statement in the code (I believe it has something to do with how the function is evaluated). That aside, it seems that there is no actual need for variable declaration (although certainly not a best practice, I can see why you’d avoid it in such a small code block).

This feels like a bug to me. Like even if you have the transformation script return a 0 (“0” works fine) the script just abandons the transformation logic and does a direct mapping of source to target values. That has implications for numeric target data types as well.

The issue is how this type is converted - e.g this is Rhino scripting - so would it return a Boolean or boolean to the sync engine? I’ll bet the latter. The sync transform, if I am correct, is expecting Object or JsonValue; boolean is not an Object so a cast exception must be happening.
And with String - I would assume that the ICF framework is able to properly cast it to the correct target type.
Might be worth trying to return a Boolean value actually.

I gave that a shot like this:

if (source == "active") {
    active = java.lang.Boolean.TRUE;
} else {
    active = java.lang.Boolean.FALSE;
}
active;

Unfortunately the result was still the same - true works as expected, false just results in the source value being assigned to the target. I also tried:

    active = java.lang.Boolean(false);

but that also did not work.

Just grabbed some configuration I used for a scripted SQL connector, and this have been working :

{
      "default": false,
      "source": "",
      "target": "inactive",
      "transform": {
            "globals": {},
           "source": "source.accountStatus === \"ACTIVE\" ? false : true",
            "type": "text/javascript"
      }
}

What version do you have that working on? I’m testing this out on 7.2.1 and I still see the same result.

ForgeRock Identity Cloud [ so Likely 7.3.x ] Might be worth looking for an existing closed JIRA.

Actually, can we be sure that the issue lies with the sync engine rather than the connector? e.g differences are:

  • 7.2.1 vs 7.3 (FIDC)
  • SCIM versus SQL Scripted

Interestingly, I just tried this on 7.3.0 (revision: 0cee568) and got the same issue. I’m fairly certain that this is not a connector issue as I’ve tried this with a couple of different sources and targets (managed objects, LDAP system target, and flat file system target). I don’t think that this would be data type specific since true works without issue, it’s just false. I don’t even think the target connector is invoked when it does the preview of the transformation.

Here is a mapping from managed/user to a custom type managed/custom

{
            "target" : "managed/custom",
            "source" : "managed/user",
            "name" : "managedUser_managedCustom",
            "displayName" : "managedUser_managedCustom",
            "properties" : [
                {
                    "target" : "inactive",
                    "source" : "",
                    "transform" : {
                        "type" : "text/javascript",
                        "globals" : { },
                        "source" : "source.accountStatus === \"ACTIVE\" ? false : true"
                    },
                    "default" : false
                },
                {
                    "target" : "uid",
                    "source" : "userName"
                }
            ],
            "policies" : [
                {
                    "action" : "EXCEPTION",
                    "situation" : "AMBIGUOUS"
                },
                {
                    "action" : "EXCEPTION",
                    "situation" : "SOURCE_MISSING"
                },
                {
                    "action" : "EXCEPTION",
                    "situation" : "MISSING"
                },
                {
                    "action" : "EXCEPTION",
                    "situation" : "FOUND_ALREADY_LINKED"
                },
                {
                    "action" : "DELETE",
                    "situation" : "UNQUALIFIED"
                },
                {
                    "action" : "EXCEPTION",
                    "situation" : "UNASSIGNED"
                },
                {
                    "action" : "EXCEPTION",
                    "situation" : "LINK_ONLY"
                },
                {
                    "action" : "IGNORE",
                    "situation" : "TARGET_IGNORED"
                },
                {
                    "action" : "IGNORE",
                    "situation" : "SOURCE_IGNORED"
                },
                {
                    "action" : "IGNORE",
                    "situation" : "ALL_GONE"
                },
                {
                    "action" : "UPDATE",
                    "situation" : "CONFIRMED"
                },
                {
                    "action" : "UPDATE",
                    "situation" : "FOUND"
                },
                {
                    "action" : "CREATE",
                    "situation" : "ABSENT"
                }
            ]
        }

And that works as expected with IDM 7.3 [ from backstage downloads ]

My environment:

  • MacBookPro 2018
  • Java:

openjdk version “11.0.9.1” 2020-11-04
OpenJDK Runtime Environment AdoptOpenJDK (build 11.0.9.1+1)
OpenJDK 64-Bit Server VM AdoptOpenJDK (build 11.0.9.1+1, mixed mode)

  • Embedded DS (not recommended)

And the corresponding managed configuration:

        {
            "name" : "custom",
            "schema" : {
                "$schema" : "http://forgerock.org/json-schema#",
                "type" : "object",
                "title" : "Custom",
                "description" : null,
                "icon" : "fa-database",
                "properties" : {
                    "inactive" : {
                        "title" : null,
                        "type" : "boolean",
                        "viewable" : true,
                        "searchable" : false,
                        "userEditable" : true
                    },
                    "uid" : {
                        "title" : null,
                        "type" : "string",
                        "viewable" : true,
                        "searchable" : true,
                        "userEditable" : true
                    }
                },
                "order" : [
                    "inactive",
                    "uid"
                ],
                "required" : [
                    "uid"
                ]
            }
        }

So what I was missing in my mapping was the default value of false, once I set that I was able to get a value of false in the target system. This still won’t work if you define a source attribute for the mapping - source must be blank.

Sounds like that is the workaround we need to use for this - any time a mapping can result in a falsy value (e.g. false, 0, -0, null, undefined) the source should not be defined and the default value should be set to the expected falsy value.

Thanks for sharing the working example!