How to Retrieve Files from Sharepoint Library with AEM Custom Component
By: Jakub Olszyna
Tags: Adobe Experience Cloud, Aem, Sharepoint, Microsoft, Microsoft Graph Api

Requirements

AEM 6.5.x

Java 11

An application registered with the Microsoft Identity Platform with the http://localhost:4502/content/example/ms-graph-callback.html added as the redirect URI [1]

added as the redirect URI [1] AEM project to work on (i.e. created using aem-project-archetype [2])

[2]) A sharepoint site (i.e. https://{hostname}/{site} )

Create component

First we need to create new AEM component that will display files from Sharepoint. Let’s add a simple html with a placeholder for the actual files and a corresponding js that will handle all the logic. Sharepoint site params (hostname, site and list) should be enough to define which documents our component should display.

<!-- sharepointFileList.html -->

<sly>

<h3>${properties.title}</h3>

<ul class="files-list"></ul> <script>

$(function() {

sharepointFileList('${properties.hostname @ context="scriptString"}', '${properties.site @ context="scriptString"}', '${properties.list @ context="scriptString"}');

});

</script>

</sly> <!-- sharepointFileList.js -->

var sharepointFileList = (hostname, site, list) => {

var _init = () => {

} _init()

}

Request user authorization

To use Microsoft Graph to read and write resources on behalf of a user, your app must get an access token from the Microsoft identity platform and attach the token to requests that it sends to Microsoft Graph.

The first step to getting an access token is to open new pop-up window and redirect the user to the Microsoft identity platform authorize endpoint with required scope. In our case it scope should at least contain Sites.Read.All to allow read-only access to users resources. The user organization is defined as the tenant and appId corresponds to the Application ID that the registration portal assigned your app.

<!-- sharepointFileList.js -->

let AUTH_ENDPOINT = https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize? client_id={appId}&response_type=code &redirect_uri=http://localhost:4502/content/example/ms-graph-callback.html&response_type=code&response_mode=fragment&state=12345&nonce=12345&scope=openid Sites.Read.All User.Read

At this point user will have to sign in to Azure AD and then give consent for the permissions requested by the app. After consent is obtained, Azure AD will return an authorization_code by redirecting to the configured redirect_uri. To properly extract the authorization code, the following component needs to be created and added to the content/example/ms-graph-callback.html node.

<!-- msGraphCallback.html -->

<sly>

<script>

var tokenSent = false function init() {

window.addEventListener('message',

(evt) => {

if (!tokenSent && evt.data == 'outlooktokenrequest') {

tokenSent = true

var originWnd = evt.source

var data = 'outlook:' + window.location.hash

originWnd.postMessage(data, '*')

}}, false);



if (window.parent) {

window.parent.postMessage('outlook:' + window.location.hash, '*')

}

} init()

</script>

</sly>

To pass the code from the pop-up window to our page we have to add an event listener [3].

<!-- sharepointFileList.js -->

window.addEventListener('message', (evt) => {

handleAuthCallback(evt, resolve, reject)

}, false)

Get user token

Y our app uses the authorization code received in the previous step to request an access token by sending a POST request to the /token endpoint.

To secure the app secret, we’ll send the token request from the servlet and save the actual token in the localStorage.

<!-- sharepointFileList.js -->

const TOKEN_ENDPOINT = '/bin/example/oauth' const handleAuthCallback = (evt, resolve, reject) => {

let msg = evt.data.toString()



if (msg && msg.indexOf('outlook:') === 0) {

loginWindow.close()

let result = deserializeHash(msg.replace('outlook:#', ''))



if (result.state == state) {

let code = result.code



$.ajax({

url: TOKEN_ENDPOINT,

type: "post",

data: `scope=openid Sites.Read.All User.Read&code=${code}&redirect_uri==http://localhost:4502/content/example/ms-graph-callback.html&grant_type=authorization_code`,

contentType: "application/x-www-form-urlencoded"

})

.done(function(resp) {

let result = JSON.parse(resp.token)



localStorage.setItem('outlookToken', result.access_token)

localStorage.setItem('outlookTokenExpiration', Date.now() + parseInt(result.expires_in) * 1000)

resolve(result)

})

.fail(function() {

reject(new Error('Couldn\'t fetch access token'))

})

} else {

reject(new Error('Control state did not match received one.'))

}

}

} <!-- MicrosoftOauthServlet.java -->

@Override

protected void doPost(final SlingHttpServletRequest req, final SlingHttpServletResponse resp) throws IOException {

String scope = req.getParameter("scope");

String code = req.getParameter("code");

String redirectUri = req.getParameter("redirect_uri");

String grantType = req.getParameter("grant_type");



ByteArrayOutputStream out=new ByteArrayOutputStream();

ObjectMapper objectMapper = new ObjectMapper();

try {

final HttpClient httpClient = new HttpClient();

httpClient.getHttpConnectionManager().getParams().setConnectionTimeout(10000);

httpClient.getHttpConnectionManager().getParams().setSoTimeout(10000);



final PostMethod method = new PostMethod(AUTH_URL);

method.addRequestHeader(HttpHeaders.CONTENT_TYPE, "application/x-www-form-urlencoded");



method.addParameter("client_id", APP_ID);

method.addParameter("scope", scope);

method.addParameter("code", code);

method.addParameter("redirect_uri", redirectUri);

method.addParameter("grant_type", grantType);

method.addParameter("client_secret", APP_SECRET);



int success =httpClient.executeMethod(method);



resp.setContentType("application/json");

resp.setStatus(success);



objectMapper.writeValue(out, objectMapper.createObjectNode().put("token", IOUtils.toString(method.getResponseBodyAsStream(), "UTF-8")));

} catch(Exception e) {

log.error("*[MicrosoftOauthServlet]* Error while fetching token `{}`", e.getMessage(), e);

resp.setContentType("application/json");

resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);

objectMapper.writeValue(out, e.getMessage());

}



resp.setContentType("application/json");

resp.getWriter().write(out.toString());

}

Fetch file data

Once we have the token, we can use it to retrieve the site id (using the hostname and site name defined as component properties) which is then required to fetch the files.

<!-- sharepointFileList.js -->

const getSiteId = () => {

return new Promise((resolve, reject) => {

$.ajax({

url: `https://graph.microsoft.com/beta/sites/${hostname}:/${site}?$select=id`,

type: 'get',

headers: {

'Authorization':`Bearer ${localStorage.getItem('outlookToken')}`,

}

})

.done(function(resp) {

resolve(resp)

})

.fail(function() {

console.error('Couldn\'t get Sharepoint site ID')

localStorage.setItem('outlookToken', '')



reject(new Error('Couldn\'t get Sharepoint site ID'))

})

})

}

const importFilesFromLocation = async (siteId) => {

$.ajax({

url: `https://graph.microsoft.com/beta/sites/${siteId}/lists/${list}/items?expand=fields`,

type: 'get',

headers: {

'Authorization':`Bearer ${localStorage.getItem('outlookToken')}`,

}

})

.done(function(resp) {

const m = new moment(file.fields.Created)

const createdDate = m.format("ddd, MMMM D")



$(".files-list").append(`<li>

<div>

<div class="file-name">${file.fields.LinkFilename}</div>

<span class="file-date">${createdDate}</span></div>`+

'<div><button class="download" onclick="window.location=\''+file.webUrl+'\'">Download</button></div></li>')

})

.fail(function() {

console.error('Couldn\'t read Sharepoint files')

localStorage.setItem('outlookToken', '')

})

}

By adding some styling and file images, we can make the actual output look like this.