Setting User Avatar via ScriptRunner

scott_boisvert
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
June 3, 2020

We recently moved from a Server environment to a Data Center environment for Jira. We kept our Server licenses for some proof of concept work. With Scriptrunner's new LDAP resource I was able to write a script to pull user profile pics from Active Directory and update their profiles in Jira and Confluence. I did this in our Data Center environment and works great. I'm currently running a proof of concept in our server environment with Jira Service Desk. So I thought I would put the script in there as well. However, when running the script in the server environment, the avatars are getting set to the silhouette with a question mark in it instead of the profile picture. When I go to /atlassian/application-data/jira/data/avatars directory, all the images are there and contain the user's profile picture from AD. Checking the users avatar ID after assigning it, points to the correct avatars. I can't find any differences between my Data Center and Server installs other than the fact that they are Data Center and Server. I even tested in my Development Data Center environment by installing a trial of service desk there. Anyone aware of any difference between the two or if there is a permission issue I'm missing? Oh, and if I set to one of the system avatars it works fine.

This is the avatar that is displayed after running the script: image.png

Jira Version: 8.7.1

Script Runner Version: 6.1.0-p5

For reference the code for the script is here:

import com.onresolve.scriptrunner.ldap.LdapUtil
import org.springframework.ldap.core.AttributesMapper
import javax.naming.directory.SearchControls
import javax.naming.directory.Attributes
import com.atlassian.jira.avatar.Avatar
import com.atlassian.jira.avatar.AvatarManager
import com.atlassian.jira.avatar.AvatarService
import com.atlassian.jira.user.ApplicationUser
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.icon.IconType
import com.atlassian.jira.icon.IconOwningObjectId
import java.awt.image.BufferedImage
import java.io.File
import javax.imageio.ImageIO

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.bc.JiraServiceContext
import com.atlassian.jira.bc.JiraServiceContextImpl

import com.atlassian.crowd.manager.directory.DirectoryManager
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.user.util.UserManager
import com.atlassian.jira.icon.IconType
import com.atlassian.jira.avatar.AvatarImpl

import com.atlassian.jira.avatar.Avatar.Size

def userManager = ComponentAccessor.getUserManager()
AvatarService avatarService = ComponentAccessor.getAvatarService()

userManager.getAllApplicationUsers().each { user ->

def avatarManager = ComponentAccessor.getAvatarManager()
def avatars = avatarManager.getCustomAvatarsForOwner(IconType.USER_ICON_TYPE, user.name)

avatars.each {avatar ->
if (!avatar.isSystemAvatar() && avatar != null)
{
avatarManager.delete(avatar.id)
}
}

if (user.active)
{
def noPhoto = false
def cnList

if (user.directoryId == 10300)
{
cnList = LdapUtil.withTemplate("GMCC") { template ->
template.search("", "(sAMAccountName=" + user.name + ")", SearchControls.SUBTREE_SCOPE, { attributes ->
if(attributes.get('thumbnailPhoto') != null)
{
noPhoto = true
attributes.get('thumbnailPhoto').get()

}

} as AttributesMapper<byte[]>)
}
}
else if (user.directoryId == 10400)
{
cnList = LdapUtil.withTemplate("IMICO") { template ->
template.search("", "(sAMAccountName=" + user.name + ")", SearchControls.SUBTREE_SCOPE, { attributes ->
if(attributes.get('thumbnailPhoto') != null)
{
noPhoto = true
attributes.get('thumbnailPhoto').get()
}

} as AttributesMapper<byte[]>)
}
}

if (noPhoto)
{
ByteArrayInputStream bis = new ByteArrayInputStream(cnList[0] as byte[])
BufferedImage bImage = ImageIO.read(bis)
ImageIO.write(bImage, "jpg", new File("/data/atlassian/application-data/jira/scripts/data/test.jpg"))

def fileName = "test.jpg"
File file = new File("/data/atlassian/application-data/jira/scripts/data/test.jpg")

def contentType = AvatarManager.PNG_CONTENT_TYPE
IconType iconType = IconType.USER_ICON_TYPE
IconOwningObjectId owner = new IconOwningObjectId(user.name)
BufferedInputStream iStream = new BufferedInputStream(new FileInputStream(file))
Avatar avatar = avatarManager.create(fileName, contentType, iconType, owner, iStream, null)
avatarService.setCustomUserAvatar(user, user, avatar.getId())
}
}
}

3 answers

1 accepted

0 votes
Answer accepted
scott_boisvert
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
June 5, 2020

I figured this out. I'm not sure I understand why, but setting the IconOwningObjectId with the Jira Administrator account and the remote user on the setCustomUserAvatar call to the Jira Administrator it worked.

Gennady Makhnach April 4, 2023

In this case all avatars will be stored under Administrator profile and user avatars will be 'linked' to them.

It can be a bit confusing for users.

0 votes
MarcoFranzen April 5, 2024

We have now set this up in exactly the same way with a scheduled job. Thanks for the article, it was very helpful :)

Here is an updated version of the script, the main differences are:

import org.springframework.ldap.core.LdapTemplate
import com.onresolve.scriptrunner.ldap.LdapUtil
import org.springframework.ldap.core.AttributesMapper
import javax.naming.directory.SearchControls
import com.atlassian.jira.avatar.AvatarManager
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.icon.IconType
import com.atlassian.jira.icon.IconOwningObjectId
import com.google.common.io.ByteSource

final USERDIRECTORY_ID = 00000
final AD_RESOURCE_NAME = "YourAdResouceName"

def userManager = ComponentAccessor.getUserManager()
def avatarService = ComponentAccessor.getAvatarService()
def avatarManager = ComponentAccessor.getAvatarManager()


userManager.getAllApplicationUsers().each { user ->
    // ignore inactive users
    if (!user.active) {
        return
    }

    // ignore other user directories
    if (user.directoryId != USERDIRECTORY_ID) {
        return
    }

    // get the profile picture from the AD
    def hasPhoto = false
    def cnList = LdapUtil.withTemplate(AD_RESOURCE_NAME) {
        template ->
            (template as LdapTemplate).setIgnorePartialResultException(true)
            template.search("", "(sAMAccountName=" + user.name + ")", SearchControls.SUBTREE_SCOPE, {
                    attributes ->
                    final thumbnailPhotoAttribue = 'thumbnailPhoto'
                    if (attributes.get(thumbnailPhotoAttribue) != null) {
                        hasPhoto = true
                        attributes.get(thumbnailPhotoAttribue).get()

                    }
                }
                as AttributesMapper <byte[]> )
    }

    // return if no profile picture was found
    if (!hasPhoto) {
        return
    }

    // delete the old avatars of the user
    def avatars = avatarManager.getCustomAvatarsForOwner(IconType.USER_ICON_TYPE, user.key)
    avatars.each {
        avatar ->
            if (!avatar.isSystemAvatar() && avatar != null) {
                avatarManager.delete(avatar.id)
            }
    }

    // upload avatar to jira
    def owningObjectId = new IconOwningObjectId(user.key)
    def fileName = user.key + ".jpg"
    def avatar = avatarManager.create(fileName, AvatarManager.PNG_CONTENT_TYPE, IconType.USER_ICON_TYPE, owningObjectId, ByteSource.wrap(cnList[0] as byte[]).openStream(), null)
    avatarService.setCustomUserAvatar(user, user, avatar.getId())
}

 

 

For all those who want to set it up in the same way, here are a few more steps on how to set up the LDAP resource and the scheduled job. Even if it is almost self-explanatory:

 

Setup LDAP Connection Resource for Scriptrunner
Browse to "ScriptRunner > Resources > Create Resource > LDAP connection" to create the LDAP Resource. The name should match the name that you use in your groovy script
Create a Scheduled Job
Browse to "Browse to ScriptRunner > Jobs > Create Job > Custom scheduled Job" to create a Job. Name and Executing User doesn't really matter.
To run it every day at midnight, you can use the Con expression 0 0 0 ? * *

 

 

Dont forget to replace the AD Resource Name und Userdirectory ID with your values ;)

 

0 votes
Gennady Makhnach April 4, 2023

The original code works for me with one small change:

IconOwningObjectId owner = new IconOwningObjectId(user.name.toLowerCase())


It looks like internally search algorithm for user avatar by user name is case-sensitive but Jira uses User Name in lower case to search. So avatar.owner field should be stored in lower case in database to work correctly.

Suggest an answer

Log in or Sign up to answer