The LDAP Picker scripted field displays LDAP records returned by a pre-configured search query from a connected LDAP server.

Examples for usage include:

Before creating an LDAP Picker field, you must set up a connection to the target LDAP server in the Resources tab.

Usage

  1. Enter the field name, and select the LDAP server this field will work with.

  2. Enter a search query. You can use this search query to specify a base DN for the query, to select the objectClass, and any other criteria you need.

    See the Code Snippets section under the editor for examples.
    For help with the query, you should contact your Directory Services department, or a local subject matter expert.
  3. Specify the Search Attribute. This attribute will be appended to the query when the Jira user starts typing. If the field is for picking a user, then searching on surname (the LDAP attribute is typically sn) is your best choice.

  4. Specify the Display Attribute. This is the attribute that is used for displaying, both in the select box, and in the View Issue screen.

Example field configuration:

ldap picker setup

and in use:

ldap picker usage
This is a new feature under active development. Please contact us to let us know what you need.

Customisations

You can customise the picker in many ways using the Configuration Script option. However, it’s recommended you get the basic functionality of your picker working before you work on advanced customisation.

Configure advanced functionality by editing the configuration script, and specifying closures or string values. For operations like rendering the display value, the closure is called (if present), along with any arguments required. The following information explains what happens by default, and what arguments are available to your closure, as well as usage examples.

You do not need to include all possible closure arguments. You can omit superfluous arguments, but you must keep the order. Only drop arguments from the right of the list, for example:

myClosure = { foo, bar, baz ->

can be shortened to:

myClosure = { foo ->
    // ...
}
These customisations can be complex. Ensure you thoroughly test on a non-production system.

Avoiding Cross-Site Scripting Attacks

Cross-Site Scripting (XSS) is a type of vulnerability that arises when a web application renders data as HTML from an untrusted source.

In the example of the database picker, an attacker may enter javascript into a record in your linked database, then select that record in the database picker. That may look like:

<script src="http://bad-domain.com/stealCookies.js">An innocent looking record

If the database picker rendered the above as HTML, and the attacker persuaded a system admin to view that page, the JavaScript would run. This could allow the attacker to do things like steal cookies or execute REST requests to gain system admin permissions.

To prevent this, the HTML returned from the database or LDAP query is sanitised, that is, all JavaScript is removed, and any missing HTML tags are inserted.

Customising the Displayed Value

By default, we will show the value of the Display Attribute from the LDAP record retrieved.

To customise the display of this value, implement:

import org.springframework.LdapDataEntry

renderViewHtml = { String displayValue, LdapDataEntry ldapDataEntry, boolean active ->
    // return a String: the value that will be displayed when viewing an issue
}
displayValue

The display value that we calculated

ldapDataEntry

An LdapDataEntry

active

A boolean value indicating whether or not the records meet the criteria for an active record (see Dealing with Disabling Options below.)

You can either use that display value and add to it or just display a new value based on the LDAP result.

When viewing the field in Column view in the Issue Navigator, we will call renderColumnHtml if it is present, which has the same signature as renderViewHtml. Use renderColumnHtml if you wish to provide a smaller, simpler result suitable for display in a tabular format such as the issue navigator, or CSV export.

For example, to display the location (l attribute) as well as the user name:

import org.springframework.LdapDataEntry

renderViewHtml = { String displayValue, LdapDataEntry ldapDataEntry, boolean active ->
    "$displayValue (${ldapDataEntry.attributes.get('l').get()})"
}

Customising the HTML in the Drop-down

By default, we show the Display Attribute in the dropdown. It’s possible to add additional information to this display, which may help guide users to the choice that they are looking for.

Overriding the view HTML, following the section above, has no effect on this display.

Adding Additional Information to the Drop-down

Following the project picker example again, we can augment the list of items in the drop-down to show the project lead. To do this, implement:

import org.springframework.LdapDataEntry

renderOptionHtml = { String displayValue, LdapDataEntry row ->
    // return a String, containing HTML, that will be displayed in the drop-down
}

For example, as above, if we wanted to display the user’s location in the drop-down we could use:

import org.springframework.LdapDataEntry

renderOptionHtml = { String displayValue, LdapDataEntry ldapDataEntry ->
    "${displayValue} (${ldapDataEntry.attributes.get('l').get()})"
}

Which will display as:

ldap customise dropdown

Setting a drop-down icon

Often you may have an image for users in your LDAP directory.

You can set the avatar by implementing:

import org.springframework.LdapDataEntry

getDropdownIcon = { LdapDataEntry row ->
    // return a String: a link to an icon, or base64 encoded string beginning 'data:image/jpg;base64, '
}

For example, to show images for users, where the image is stored in an attribute named jpegPhoto:

import org.springframework.LdapDataEntry

getDropdownIcon = { LdapDataEntry ldapDataEntry ->
    def photoAttribute = ldapDataEntry.attributes.get('jpegphoto')

    photoAttribute ?
        'data:image/jpg;base64, ' + new String(Base64.getEncoder().encode(photoAttribute.get() as byte[])) :
        null
}

which will render as:

ldap customise icon

Customising the Search Filter

There are many cases for customising the filter used when searching, for instance:

  • Restricting records to just those the current Jira user has permission to see,

  • Narrowing to particular objectTypes or organisational units,

  • Restricting to just those users the current user is managed by, or a manager of.

Implement:

import com.atlassian.jira.issue.Issue
import org.springframework.ldap.filter.AndFilter

getSearchFilter = { AndFilter currentFilter, Issue issue ->
    // return org.springframework.ldap.filter.Filter. Typically you will add to the supplied AndFilter.
}
currentFilter

An instance of AndFilter, which encompasses the search filter specified in the form, and the clause for narrowing down the results based on what the user has typed

issue

The current Issue object which the field is attached to. This is a live issue, in that its values will reflect any typed into the current form

You must return an instance of Filter. Typically you would do this by adding additional clauses based on issue fields, current user roles or groups etc.

If you override this, you should also modify the validation filter (see below), as otherwise, the user could set a value (either using the REST or Java API) that they would not be able to set through the normal web user interface.

Setting Search Filter based on Issue Fields

There are some points to be aware of when using the Issue object:

  • The Issue object which is available to you is never null. When the user is interacting with the Create Issue dialog, you can use issue.issueType and issue.projectObject to get the current issue type and project, but issue.isCreated() will be false.

Note that in both of the following cases, we will use the search filter from the configuration form, rather than the script:

  • When setting a default value via Admin → Custom Fields

  • When searching for issues in the issue navigator

You need to keep the return value from this closure and the search filter in the configuration form in sync…​ The search filter in the form should be a superset of all the possible values that can be returned from your customised filter, to allow users to find any possible value.

In the following example, the LDAP search filter is modified according to the value of a custom field named Organisational Unit, allowing the end-user to further filter on ou :

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.Issue
import org.springframework.ldap.filter.EqualsFilter
import org.springframework.ldap.filter.AndFilter

def customFieldManager = ComponentAccessor.customFieldManager

getSearchFilter = { AndFilter currentFilter, Issue issue ->
    def ouCustomField = customFieldManager.getCustomFieldObjects(issue).findByName('Organisational Unit')
    def ou = issue.getCustomFieldValue(ouCustomField) as String
    currentFilter.and(new EqualsFilter('ou', ou))
}

If the value of Organisational Unit is empty, the ou will be null; therefore no results are shown.

The value of the issue attributes change as the user edits the form. So this allow you to return parameterised results based on other field changes that the user is currently making.

Setting the Validation Filter based on Issue Fields

This is pretty much the same as setting the search filter above, and should normally go hand-in-hand with it. Any value that a user could pick through the search dropdown should not be invalid, unless they have subsequently changed other fields on the form that make it invalid.

To set the filter for validation, implement:

import com.atlassian.jira.issue.Issue
import org.springframework.ldap.filter.AndFilter

getValidationFilter = { AndFilter currentFilter, Issue issue ->
    // return org.springframework.ldap.filter.Filter. Typically you will add to the supplied AndFilter.
}
currentFilter

An instance of AndFilter, which encompasses the search filter specified in the form. The filter will be executed with a base DN of the DN of the record selected, to determine whether it is valid or not.

Following the above example, this validator checks that the selected value matches the selected organisational unit:

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.Issue
import org.springframework.ldap.filter.EqualsFilter
import org.springframework.ldap.filter.AndFilter

def customFieldManager = ComponentAccessor.customFieldManager

getValidationFilter = { AndFilter currentFilter, Issue issue ->
    def ouCustomField = customFieldManager.getCustomFieldObjects(issue).findByName('Organisational Unit')
    def ou = issue.getCustomFieldValue(ouCustomField) as String
    currentFilter.and(new EqualsFilter('ou', ou))
}
As, in this case, the code is identical to that used for getSearchFilter, you could just extract the body of the closure to a new method.

Dealing with Disabling Options

In some cases, you may have linked records that become invalid over time. For example, discontinued products, or users who have left the company.

You don’t want these to be selectable on new issues, but you need to retain their value on existing issues.

The method below describes how to disable values in a similar manner to that of select list options. Once an option has been disabled, you cannot use it in new issues. However, if an issue already has that field value, you can continue to save the disabled value. If the field value is changed and saved, the disabled value will no longer show as an option. This behaviour is the least surprising to users, and mirrors the way that the Disabled Options of single/multi-select pickers work.

To handle options that might become disabled, set the main search query in the form to all possible values, not taking into account the attribute that will determine whether they are active or not.

Then use the following closure to return a Filter, which will be ANDed with the search filter to define those records considered to be active.

For example, your search query may be (objectClass=inetOrgPerson), and then specify only users who are not locked out by using :

import org.springframework.ldap.filter.PresentFilter

getActiveRecordsFilter = {
    new PresentFilter('pwdAccountLockedTime')
}
If you use getActiveRecordsFilter, the display attribute used in the form must be a unique identifier for that record, for example uid or sAmAccountName. Note that you do not need to display this attribute, as you can override it using Customising the Displayed Value.

Example

In this scenario, take the example where you want to allow the Jira-user to select from the direct reports of a manager, perhaps the current user. Over time, people’s managers may change, meaning they would not be eligible to be selected for new issues, but we want to retain their value when used on any existing issue.

The Retrieval/Search Filter in the form is set to (objectClass=inetOrgPerson), and the configuration script looks like:

import org.springframework.LdapDataEntry
import org.springframework.ldap.filter.EqualsFilter

getActiveRecordsFilter = {
    new EqualsFilter('manager', 'cn=Charmian Shuler,ou=Payroll,dc=example,dc=org')
}

renderViewHtml = { String displayValue, LdapDataEntry ldapDataEntry, boolean active ->
    "$displayValue ${active ? '' : '(no longer managed by Charmian)'}"
}

Note that if the record has become inactive, a message is added to the view HTML.

Sorting in JQL Queries

If you wish to be able to sort on the value of a LDAP Picker field in a JQL query, you must specify on which attribute the sorting should be done.

To do that, set a value for sortAttribute.

For example, if you are querying users and wish to sort by surname use:

sortAttribute = 'sn'

If sortAttribute is not specifying, sorting will be by the dn of the linked LDAP entry. This will be consistent, but may not be what the user expects given the value displayed for the field.

There will be a small overhead to specifying a sort value, as an LDAP query will be done each time the issue is re-indexed.

Users cannot sort by arbitrary values of the record. If you change the sort value, you will not get accurate sorting results until all issues with this field are reindexed.

You could do that by using the reindex issues script with a JQL query similar to MyLDAPPickerField is not empty. Or, a full reindex.

Miscellaneous Configuration

Multiple Values Delimeter

Multiple values are separated with a comma. To change this set multiValueDelimiter to a string. For example, if you are rendering values as lozenges, you might set this to the empty string, or to set the joiner to a | add the following:

multiValueDelimiter = "|"
Number of Values in Drop-Down

By default, a maximum of 30 values will be retrieved and displayed in the drop-down. To change that set maxRecordsForSearch to a number.

Have questions? Visit the Atlassian Community to connect, share, and learn with other Atlassian users and experts, including Adaptavist staff.

Ask a question about ScriptRunner for JIRA, Bitbucket Server, or Confluence.

Want to learn more? Check out courses on Adaptavist Learn, an online platform to onboard and train new users for Atlassian solutions.