Skip to content

Commit

Permalink
Adding new best practices article for secure file access tips (#212)
Browse files Browse the repository at this point in the history
  • Loading branch information
jimj-procoretech authored Jan 24, 2024
1 parent 308cfc5 commit 03bac43
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 2 deletions.
2 changes: 2 additions & 0 deletions _data/navigation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@
url: /mpz-headers
- title: Working with Documents
url: /tutorial-documents
- title: Working with Secure File Access
url: /secure-file-access-tips
- title: API Call Sequencing
url: /api-call-sequencing

Expand Down
136 changes: 136 additions & 0 deletions best_practices/secure_file_access_tips.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
---
permalink: /secure-file-access-tips
title: Working with Secure File Access
layout: default
section_title: Best Practices

---

## Overview

Below are a some valuable tips for working with secure file access and the Procore API.

## Handling URL Schema Changes

As indicated in the ["File Link URL Schema Change"]({{ site.url }}{{ site.baseurl }}{% link overview/announcements.md %}#file-link-url-schema-change) announcement posted 06/28/2023, developers should not assume any particular hostnames for download links because they can change in future, both due to Procore backend changes as well as changes from our cloud service partner, Amazon Web Services.

## Passing the Bearer Token for File Downloads

As described in the ["Bearer Token to be Required for File Access Authorization"]({{ site.url }}{{ site.baseurl }}{% link overview/announcements.md %}#bearer-token-to-be-required-for-file-access-authorization) announcement posted 06/10/2022, we require developers to include the Procore Bearer Token for accessing any file or document URL.
One important detail is that files are not necessarily served directly by those URLs.
For example, clients currently get redirected to our cloud service partner, Amazon Web Services, to actually fetch file content.
It is therefore important to only include Procore Bearer Tokens in requests to Procore domains (*.procore.com) and not to include them in requests going elsewhere (also due to automatic redirection).
Different HTTP clients behave differently with respect to re-attaching “Authorization” HTTP header in case of redirects.
You should check the documentation for your particular client.
For convenience, we include below an explicit implementation using the standard `java.net.http.HttpClient`.

### Example Implementation

The following example implementation is based on `java.net.http.HttpClient` from Java 17.0.2.

Downloading occurs in the `downloadFile` method that accepts:

1. `fileUrl` is a link for file download.
You can obtain it from a Procore API response.
For example, the response for `GET /rest/v1.0/folders` contains this url under `files[].file_versions[].url` (Note: most of the fields are omitted in the example below):

```
{
"id": 1,
"files": [
{
"id": 12,
"name": "file.pdf",
"file_versions": [
{
"id": 12,
"notes": "These are notes about the current file version.",
"url": "www.file.com",
}
]
}
]
}
```

2. `bearerTokenValue` is a valid Procore [OAuth 2.0 Access Token]({{ site.url }}{{ site.baseurl }}{% link oauth/oauth_access_tokens.md %}).

### Complete Listing

```
package com.procore.file_access_demo;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.Optional;
import java.util.Set;
public class DownloaderApp {
// Can be taken from response body of
// https://developers.procore.com/reference/rest/v1/project-folders-and-files?version=1.0#list-project-folders-and-files
private static final String FILE_URL = "";
private static final String BEARER_TOKEN_VALUE = "";
public static void main(String[] args) throws Exception {
downloadFile(FILE_URL, BEARER_TOKEN_VALUE);
}
private static void downloadFile(String fileUrl, String bearerTokenValue) throws URISyntaxException, IOException, InterruptedException {
// Create an HttpClient that doesn't follow redirects
// to make sure we don't send Bearer token to non-Procore domains
HttpClient client = HttpClient.newBuilder()
.followRedirects(HttpClient.Redirect.NEVER)
.build();
// Perform the initial request
HttpRequest request = HttpRequest.newBuilder()
.uri(new URI(fileUrl))
.header("Authorization", "Bearer %s".formatted(bearerTokenValue))
.build();
HttpResponse<InputStream> response = client.send(request, HttpResponse.BodyHandlers.ofInputStream());
// Loop through redirects preventing infinite loops
int maxRedirects = 5;
int redirectCount = 0;
while (Set.of(301, 302, 303, 307, 308).contains(response.statusCode())
&& (redirectCount++ < maxRedirects)) {
Optional<String> location = response.headers().firstValue("Location");
if (location.isEmpty()) {
throw new RuntimeException("Redirect response did not contain Location header");
} else {
URI redirectLocation = new URI(location.get());
HttpRequest.Builder redirectRequestBuilder = HttpRequest.newBuilder()
.uri(redirectLocation);
// Only include Bearer token for Procore domains
String procoreDomain = ".procore.com";
if (redirectLocation.getHost().endsWith(procoreDomain)) {
redirectRequestBuilder.header("Authorization", "Bearer %s".formatted(bearerTokenValue));
}
// Follow the redirect
response = client.send(redirectRequestBuilder.build(), HttpResponse.BodyHandlers.ofInputStream());
}
}
if (response.statusCode() == 200) {
// Save the file to disk
Files.copy(response.body(), Path.of("/tmp/file.bin"), StandardCopyOption.REPLACE_EXISTING);
}
}
}
```

## Testing Your Integration

We are currently working on enabling this new functionality in Monthly and Developer Sandboxes where you will be able to test your integrations.
4 changes: 2 additions & 2 deletions overview/announcements.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ section_title: Overview
>
>If you have questions regarding this change, please reach out to [[email protected]](mailto:[email protected]).
>**File Link URL Schema Change** (06/28/2023)
><a id="file-link-url-schema-change" />**File Link URL Schema Change** (06/28/2023)
>
>Starting June 30, 2024, the URL schema for file API responses and redirects will be changing.
>Today, file links have the following structure: `https://storage.procore.com/…` and they redirect to `https://s3.amazonaws.com/…`.
Expand Down Expand Up @@ -122,7 +122,7 @@ section_title: Overview
>If you have yet to incorporate the `Procore-Company-Id` request header into your integration designs, we encourage you to do so as soon as possible.
>If you have questions regarding this requirement, please reach out to [[email protected]](mailto:[email protected]).
>**Bearer Token to be Required for File Access Authorization** (06/10/2022)
><a id="bearer-token-to-be-required-for-file-access-authorization" />**Bearer Token to be Required for File Access Authorization** (06/10/2022)
>
>Starting June 1, 2023 an authorization bearer token will be required in the request header for accessing any Procore file or document URL.
>Today, an authorization token is not required to access a file directly from storage.procore.com, but it will be a requirement in the future in order to strengthen file access security.
Expand Down

0 comments on commit 03bac43

Please sign in to comment.