Forge Confluence CustomUI baseUrl (or alternative)

I am attempting to render user information, including the Avatar of the user (if available). Whilst i can get the profilePicture information from the user JSON, it does not include the actual instance information.
I have seen how to retrieve baseUrl for Jira but cannot rely on the requestJira Fetch API.

Is there a Confluence alternative for retrieving the instances baseUrl when using Forge CustomUI? (Or am i going about this wrong? Hitting <myinstance-baseUrl>/<userJson-profileUrl> in browser returns the correct Avatar icon, but without the baseUrl the Avatars will be rendered as anonymous users)

@DennyMiller hey, I’m on the Forge team. Could you please share the JSON that you get from Confluence API? My understanding was that the profile icons should be absolute URLs according to the docs.

Sure, here is a single users redacted information JSON.

{
	type: 'known',
	accountId: '<ID>',
	accountType: 'atlassian',
	email: '<EMAIL>',
	publicName: '<NAME>',
	profilePicture: {
		path: '/wiki/aa-avatar/5d6cd2ca8f0aa30dba0af03c',
		width: 48,
		height: 48,
		isDefault: false
	},
	displayName: '<NAME>',
	isExternalCollaborator: false,
	_expandable: {
		operations: '',
		personalSpace: ''
	},
	_links: {
		self: '<SELF-URL>'
	}
}

Any <> tags removed user information, but the profilePicture path is still shown. If i append this to my dev instance baseUrl in browser, it renders the icon.

Attempting to use Atlaskits’ Avatar presents the following error, even if using hard-coded absolute URLS (again <> redacted information):

Content Security Policy: The page's settings blocked the loading of a resource at <baseUrl>/wiki/aa-avatar/5d6cd2ca8f0aa30dba0af03c ("img-src").

Edit I should also point out this is using the get Group members endpoint docs
Using the endpoint you shared above, the path is the same, but _links contains a ‘base’ value that holds the baseUrl - get group members does not include this property

@DennyMiller this is interesting as I’m not a pro in Custom UI. However, just a sanity check question, did you set the img-src CSP in your manifest.yml with the base domain for these avatars?

At current, not yet but it was my next step to try to get this thing working (gimme a mo and i’ll give you an answer on if it changes anything).

I would question why we would need to expose the app using External Egress Permissions to hit the dev instance the app is installed on? (This may be something users raise an eyebrow at when allowing access if they see external permissions to access their own instance)

Adding the BaseURL img-src CSP into the manifest file (and redeploying) removes the Browser error, but the Avatar icons are still returning as anonymous icons - this is tested with:

  • The path provided from the user JSON
  • The BaseURL hard coded before the user JSON path
  • The full BaseURL hardcoded

Edit To further test, a full jpg img location was added to the manifest and tested with a hardcoded URL including the baseURL hardcoded - still returning grey anonymous user icon

@DennyMiller this feels more like a wrong URL. Could you please share the URL of the avatar which displays an anonymous icon?

Sure thing, and apologies in advance for such a long detailed response!

If using the Atlaskit Avatar/AvatarIcon example, the icon will render with the image using the following:

Docs: https://atlassian.design/components/avatar/examples#circle

Manifest:

  external:
    images:
      - 'https://pbs.twimg.com/profile_images/803832195970433027/aaoG6PJI_400x400.jpg'

Component:

 <Avatar
                            appearance="circle"
                            src="https://pbs.twimg.com/profile_images/803832195970433027/aaoG6PJI_400x400.jpg"
                            size="large"
                            name="John Doe"
                        />

This works, but isn’t what we require (especially when trying to use the icon path in the user JSON.

Upon further investigation, using a URL of "< instance >/< user path form json > " requires the manifest to include the dev instance, along with any redirect domains the avatar icon is stored at - For example:

-Using a basic avatar containing the first letter of Users names:

Hardcoding the baseURL + path provided in JSON redirects to the following domain:

https://i0.wp.com/avatar-management--avatars.us-west-2.prod.public.atl-paas.net/initials/DM-1.png?ssl=1

To get this to work, both the instance and https://i0.wp.com/ are needed in the manifest.

-Using an icon avatar:

Hardcoding the baseURL + path provided in JSON redirects to the following domain:

https://avatar-management--avatars.us-west-2.prod.public.atl-paas.net/557058:fe1e10e7-d931-49b4-816a-cc8517da2177/2f822c13-efcc-416d-9b75-f4f90c25c941/128

To get this to work, both the instance and https://avatar-management--avatars.us-west-2.prod.public.atl-paas.net/557058:fe1e10e7-d931-49b4-816a-cc8517da2177/ are needed in the manifest

Conclusion

This is just using 2 example avatars, both of which require an additional external domain on top of the instance being used. Not only that, but in doing so raises a few questions:

  • How exactly can we add in the Users instance into the manifest if using a production version? (For now, it’s hardcoded using my dev instance, meaning it only works on that instance, correct?)
  • Surely the solution is not "Just keep adding external img-scp’s and re-deploy anytime you cannot render a specific avatar? (At this point is it not possible to allow UI-Kit extension from within CustomUI? Specifying an avatar using a User ID is much simpler, but the functionality for our app requirements just isn’t there with UI-Kit)
  • Even if this is the solution and just something we have to manage whenever icons cannot be found, the original problem of ‘we still cannot access the baseURL of the instance we are in’ still exists. At the time of writing, Confluence Cloud Rest API offers no way of retrieving this information, whilst Jira’s API has access to this.

Many thanks for getting to the end of my long response, hoepfully it gives an insight into the Use Case/The problem occurring. You may not be able to provide a response with things in the response, but that’s okay as any problems/current lack of support will be compiled for CodeGeist :slight_smile:

@DennyMiller

  1. Why do you need instance URL in permissions to display avatars with URLs like https://avatar-management--avatars.us-west-2.prod.public.atl-paas.net/557058:fe1e10e7-d931-49b4-816a-cc8517da2177/ - my understanding is that this URL doesn’t redirect and just returns binary data.
  2. According to your message above the avatar URLs looked different: /wiki/aa-avatar/:id. Was the URL above just an example?
  1. Because the path in the provided User JSON is the instance relative path /wiki/aa-avatar/... which when added onto the instance URL will redirect to the https://avatar-management--avatars.us-west-2.prod.public.atl-paas.net/557058:fe1e10e7-d931-49b4-816a-cc8517da2177/ URL (This is just one example).

  2. Regarding point 1, <instance-baseURL > plus the path in User JSON provided (the /wiki/aa-avatar/... ) will redirect to the avatar-management URL (for example)… I have tried with multiple avatars (both default avatars and those the User has changed).

For Proof of Concept, rendering these icons is not a huge priority, so I will most likely leave this until a future point - perhaps with answers to my previous comment this functionality may be explained a bit better in regards to why we would need to add external img-src permissions into the manifest when attempting to access default, internal, Avatar icons.

@DennyMiller now I got it. Thanks for explaining. I believe this is a problem on the edge of Forge and product API. We actually have a team that can help here. I will let them know about this problem.

UPD: out of interest, did you try using *.atlassian.net in your permissions.external.images? This should work for all installations if I’m not mistaken.

image

I can confirm that using ‘*.atlassian.net’ allows all instances as they are subdomains, however this is not the problem so I think I need to step back a bit and re-state the current problem in simpler terms:

In theory
If i want to retrieve a users avatar, I can use the path provided in the Users JSON. Let me provide a few examples of User Avatars:

User A

User B

This, along with the Non-default path that redirected to the following link (using Test Instance + path):

https://avatar-management--avatars.us-west-2.prod.public.atl-paas.net/557058:fe1e10e7-d931-49b4-816a-cc8517da2177/2f822c13-efcc-416d-9b75-f4f90c25c941/128

The Problem(s)

  • The Atlaskit <Avatar> component will only display the image if I hardcode my Test Instance’s baseURL into the src property - for example:
 //The baseURL of the test instance is placed here
let baseURL = "https://TESTINSTANCE.atlassian.net";
 <Avatar
     appearance="circle"
     /*avatar src is baseUrl + path from userJSON*/
     src={`${baseUrl}/wiki/aa-avatar/5d6cd2ca8f0aa30dba0af03c"} 
     size="large"
     name="John Doe"
 />
  • The above avatar will only render when both '*.atlassian.net' AND 'https://i0.wp.com/' are added in the manifest. This is just for one avatar, and as shown with the above examples, each one would need yet another img-scp added to the manifest (what if 1000 users have different paths, you can’t expect us to add all 1000 domains into the manifest - and if so, how do we know the domains until a User provides feedback their avatar is not being displayed?)

To get a solution

  • I am still hard-coding the baseURL - without a way to retrieve the baseURL in confluence, this will only work on the hardcoded instance - if the User JSON path does not provide an Absolute URL, could you (or someone that is aware of how) please explain how we are supposed to get the absolute URL
  • Are we expected to add EVERY domain, updating the manifest as we encounter a user with a new one? - If users install the app and it says “Share data with 1000 domains outside of Atlassian” because we’ve had to keep appending new domains for every User, they won’t like seeing that (and it’s a poor way of maintaining how we allow access to the avatars?)

Perhaps going off this comment may be easier to understand the problem at hand. I’m surprised this hasn’t been spoken about before, but this is CustomUI specific - (using UIKit would allow avatar rendering from the users accountId)… Maybe consideration on allowing UIKit components to be used in CustomUI is something to look into?)

1 Like

Solution
The provided endpoints either don’t provide the absolute URL, or when used alongside the baseUrl get joined with 2 /wiki directories. Seems like a poor design overlook .

The workaround I have used is as follows:
Manifest File

  external:
    images:
      - '*.atlassian.net'
      - '*.wp.com'
  • For default avatar icons, both of the above img-scp permissions are required. This will allow the default icons to be rendered when used with the instance’s baseUrl

To retrieve the Users profilePicture path

  • This does not provide the absolute URL, but can be retrieved from the returned user JSON, for example:
/wiki/aa-avatar/60eeeae4a0de58006ab76f54

To retrieve the baseUrl

  • With the new changes to Product API without the need for Custom Resolvers.
  • The returned value also contains /wiki so needs to be stripped
 //Retrieves instance baseUrl
    useEffect(() => {
        async function getBaseUrl(){
            let newVar = await requestConfluence(`/wiki/rest/api/settings/systemInfo`);
            let baseUrl = newVar.body.baseUrl;
            //remove the '/wiki' path, or using the given url would search for instanceUrl/wiki/wiki/xyz
            let trimmedBaseUrl = baseUrl.substring(0, baseUrl.length - 5);
            setBaseUrl(trimmedBaseUrl);
        }
        getBaseUrl();
    }, []);

Avatar Rendering

  • Without stripping the wiki on baseUrl, this will not function.
 <AvatarItem
       avatar={<Avatar src={`${baseUrl + userJsonPath}`}/>}
       primaryText={"Users Name"}
/>

Would it be fair to assume that this is how it should be achieved if the returned user JSON does not provide an absolute URL? Along with the fact that combining the default baseUrl would result in an incorrect URL?

1 Like

Hey @DennyMiller, your solution seems to be a good workaround at the moment. We have this Confluence API to retrieve the base URL - and yes you would need to strip the wiki on this for the function to work.

1 Like

Maybe related issue?

1 Like

What I currently do is

const baseUrl = user?._links?.base?.split('/').slice(0, -1).join('/');
    return (
        <AvatarItem
            avatar={<Avatar src={`${baseUrl}${user?.profilePicture?.path}`} />}
            primaryText={user?.displayName || user?.publicName || user?.accountId || 'n/a'}
        />
    );

because users have _links (probably newly implemented?):

"_links":{
      "self":"https://devx.atlassian.net/wiki/rest/api/user?accountId=557058:xxxx",
      "base":"https://devx.atlassian.net/wiki",
      "context":"/wiki"
   }

Maybe that also helps someone else in the meantime :slight_smile:

2 Likes

In this future world with custom domains, how are we mean’t to handle allowing images from the host product? The URL could be from any domain, iiuc.

Also, allowing *.atlassian.net exposes one customer to items from any other customer’s instance, which is certainly better than allowing images from anywhere, but not ideal given a hacker can create a Jira instance for free.

Ideally there would be a special value we could use in the Forge manifest that stood in for the host URL. If a user views our forge app from Jira on a custom domain, then the CSP value includes that domain. I’m kinda surprised that this doesn’t already exist. Is there anything like that?

1 Like