Create protected PDFs (with an Open password and/or a Permissions password) with Adobe PDF Services API

John_w

MrExcel MVP
Joined
Oct 15, 2007
Messages
8,491
This code uses the free Adobe PDF Services API to create a protected PDF. A protected PDF can have an 'Open password' required to open the PDF and/or a 'Permissions password' to allow printing, editing and content copying in the PDF document.

Follow these steps to use the Adobe PDF Services API:

Create an Adobe Account at Adobe Account
Go to Adobe Developer Console - Loading... | Adobe Developer Console
Click APIs and Services​
Under PDF Services API, click Create Project​
Enter a Credential name​
Tick the box 'Acrobat Services: PDF Services API'​
Click Save Configured API​
Click OAuth Server-to-Server​
Copy the Client ID and Client Secret strings - these are required by the API and stored in the "Credentials" sheet in the demonstration workbook.​

The demonstration workbook contains a "Passwords" sheet, "Sheet1", "Sheet2", "Sheet3" and "Credentials". The code creates the "Credentials" sheet if it doesn't exist and prompts for the Client ID and Client Secret strings if they aren't defined.

Demo - Protect PDFs.xlsm
ABC
1Sheet NameOpen PasswordPermissions Password
2sheet1open1perm1
3Sheet2open2perm2
4Sheet3perm3
Passwords


modMain - Demonstration code which loops through the "Passwords" sheet and saves each specified sheet as a PDF and calls the Protect_PDF procedure to apply the Open Password and/or the Permissions Password to the PDF.

VBA Code:
Option Explicit

Public Sub Create_Protected_PDFs()

    Dim PDFoutputFolder As String
    Dim sheetNameCell As Range
    Dim openPassword As String, permissionsPassword As String
    Dim PDFsheet As Object
    Dim PDFfullName As String
    
    PDFoutputFolder = ThisWorkbook.Path & "\"

    Application.ScreenUpdating = False

    'In the "Passwords" sheet, loop through each sheet name in column A starting from A2, export the sheet as a PDF and protect the
    'PDF with the 'Open password' in column B or/and the 'Permissions password' in column C.
    'Either one or both of the 'Open password' and 'Permissions password' must be specified.
    
    With ThisWorkbook.Worksheets("Passwords")
    
        For Each sheetNameCell In .Range("A2", .Cells(.Rows.Count, "A").End(xlUp))
    
            openPassword = .Cells(sheetNameCell.Row, "B").Value
            permissionsPassword = .Cells(sheetNameCell.Row, "C").Value
    
            Set PDFsheet = Nothing
            On Error Resume Next
            Set PDFsheet = ThisWorkbook.Sheets(sheetNameCell.Value)
            On Error GoTo 0
    
            If Not PDFsheet Is Nothing Then
            
                PDFfullName = PDFoutputFolder & sheetNameCell.Value & ".pdf"
                PDFsheet.ExportAsFixedFormat Type:=xlTypePDF, Filename:=PDFfullName, _
                                             Quality:=xlQualityStandard, IncludeDocProperties:=True, IgnorePrintAreas:=False, OpenAfterPublish:=False
            
                'Protect the PDF using the specified password(s).  The original .pdf file is replaced by the protected .pdf file
                
                Protect_PDF PDFfullName, PDFfullName, openPassword, permissionsPassword
                
            End If
            
        Next

    End With
    
    Application.ScreenUpdating = True
    
    MsgBox "Protected PDFs created", vbInformation

End Sub

modProtectPDF - contains the Protect_PDF public procedure which follows the steps described at https://developer.adobe.com/document-services/docs/apis/#tag/Protect-PDF

VBA Code:
Option Explicit

#If VBA7 Then
    Private Declare PtrSafe Function URLDownloadToFile Lib "urlmon.dll" Alias "URLDownloadToFileA" (ByVal pCaller As LongPtr, ByVal szURL As String, ByVal szFileName As String, ByVal dwReserved As Long, ByVal lpfnCB As LongPtr) As Long
    Private Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
#Else
    Private Declare Function URLDownloadToFile Lib "urlmon.dll" Alias "URLDownloadToFileA" (ByVal pCaller As Long, ByVal szURL As String, ByVal szFileName As String, ByVal dwReserved As Long, ByVal lpfnCB As Long) As Long
    Private Declare Sub Sleep Lib "kernel32" (ByVal milliseconds As Long)
#End If


Private Type AdobeCredentialsType
    client_id As String
    client_secret As String
    access_token As String
    token_type As String
    expires_in As Long
    expiresAtDate  As Date
End Type


Public Sub Protect_PDF(PDFinputFile As String, PDFoutputFile As String, documentOpenPassword As String, permissionsPassword As String, Optional permissions As String = "")

    'Protects a PDF file using the steps described at https://developer.adobe.com/document-services/docs/apis/#tag/Protect-PDF
    
    'Parameters
    '
    'PDFinputFile           Full path of PDF file to be protected
    '
    'PDFoutputFile          Full path of protected PDF file to be created.  Can be same as PDFinputFile, in which case the original file is replaced
    '
    'documentOpenPassword   Password to open the protected PDF. Also known as User password.
    '
    'permissionsPassword    Password to secure the protected PDF with the specified permissions.  Also known as Owner password.
    '
    'permissions            Comma-separated list of permissions
    '                       Permissions to allow printing, editing and content copying in the PDF document.
    '                       Valid values: PRINT_LOW_QUALITY, PRINT_HIGH_QUALITY, EDIT_CONTENT, EDIT_FILL_AND_SIGN_FORM_FIELDS, EDIT_ANNOTATIONS,
    '                                     EDIT_DOCUMENT_ASSEMBLY, COPY_CONTENT
    '                       By default, none of the specified actions are permitted.
    '                       PRINT_HIGH_QUALITY permission includes PRINT_LOW_QUALITY permission.
    '                       EDIT_CONTENT permission includes EDIT_DOCUMENT_ASSEMBLY and EDIT_FILL_AND_SIGN_FORM_FIELDS permissions.
    '                       Permission values will only be used if permissionsPassword is not "".
    
    Const baseURL As String = "https://pdf-services.adobe.io/"
    
    Dim XMLhttpRequest As Object
    Set XMLhttpRequest = CreateObject("MSXML2.XMLHTTP")
    
    Dim credentials As AdobeCredentialsType
    Dim postData As String
    Dim uploadUri As String, locationUri As String, downloadUri As String
    Dim assetID As String
    Dim status As String
    Dim PDFbytes() As Byte
    Dim passwordProt As String
    Dim permissionsList As String, perm As Variant

    If documentOpenPassword = "" And permissionsPassword = "" Then
        MsgBox "Protect_PDF procedure terminated because either one or both of the Document Open Password (User Password) and Permissions Password must be specified.", vbExclamation, "Protect PDF"
        Exit Sub
    End If
    
    Load_Credentials credentials
    
    If credentials.client_id = "" Or credentials.client_secret = "" Then
        MsgBox "Protect_PDF procedure terminated because Client Id or/and Client Secret aren't defined.", vbExclamation, "Protect PDF"
        Exit Sub
    End If
    
    'Get new access token if there isn't an access token or it expires within the next 5 minutes
    
    If credentials.access_token = "" Or Now > DateAdd("n", -5, credentials.expiresAtDate) Then
    
        'https://developer.adobe.com/document-services/docs/overview/pdf-services-api/gettingstarted/#step-1--getting-the-access-token
    
        postData = "client_id=" & Enc(credentials.client_id) & "&client_secret=" & Enc(credentials.client_secret)
        
        With XMLhttpRequest
            Debug.Print Time; baseURL & "token"
            .Open "POST", baseURL & "token", False
            .setRequestHeader "Content-Type", "application/x-www-form-urlencoded"
            .send (postData)
            Debug.Print .status, .statusText
            'Debug.Print .responseText
            If .status = 200 Then
                credentials.access_token = GetJSONvalue(.responseText, "access_token")
                credentials.token_type = GetJSONvalue(.responseText, "token_type")
                credentials.expires_in = GetJSONvalue(.responseText, "expires_in")
                credentials.expiresAtDate = DateAdd("s", credentials.expires_in, Now)
                Save_Credentials credentials
            Else
                MsgBox "Protect_PDF procedure terminated because XMLhttp request for access_token returned status " & .status & " " & .statusText, vbExclamation, "Protect PDF"
                Exit Sub
            End If
        End With
    
    End If
    
    'https://developer.adobe.com/document-services/docs/apis/#tag/Assets
    'Request assetID and uploadUri in order to upload the PDF file
    
    postData = "{""mediaType"": ""application/pdf""}"
    
    With XMLhttpRequest
        Debug.Print Time; baseURL & "assets"
        .Open "POST", baseURL & "assets", False
        .setRequestHeader "Authorization", "Bearer " & credentials.access_token
        .setRequestHeader "X-API-Key", credentials.client_id
        .setRequestHeader "Content-Type", "application/json"
        .send (postData)
        Debug.Print .status, .statusText
        'Debug.Print .responseText
        If .status = 200 Then
            uploadUri = GetJSONvalue(.responseText, "uploadUri")
            assetID = GetJSONvalue(.responseText, "assetID")
        Else
            MsgBox "Protect_PDF procedure terminated because XMLhttp request for file upload URI returned status " & .status & " " & .statusText, vbExclamation, "Protect PDF"
            Exit Sub
        End If
    End With
    
    'Read the PDF file into a byte array

    PDFbytes = Load_File_Bytes(PDFinputFile)

    'Upload the PDF file
    
    With XMLhttpRequest
        Debug.Print Time; uploadUri
        .Open "PUT", uploadUri, False
        .setRequestHeader "Content-Type", "application/pdf"
        .send (PDFbytes)
        Debug.Print .status, .statusText
        If .status <> 200 Then
            MsgBox "Protect_PDF procedure terminated because XMLhttp file upload returned status " & .status & " " & .statusText, vbExclamation, "Protect File"
            Exit Sub
        End If
    End With
    
    'Create the Json string for the Protect PDF job, specifying the assetID obtained above, the password(s) and permissions.
    'On successful job submission you will get a status code of 201 and a response header location which will be used for polling.
    
    passwordProt = ""
    If permissionsPassword <> "" Then
        passwordProt = "  ""ownerPassword"": " & """" & permissionsPassword & """"
    End If
    If documentOpenPassword <> "" Then
        If passwordProt <> "" Then passwordProt = passwordProt & "," & vbCrLf
        passwordProt = passwordProt & "  ""userPassword"": " & """" & documentOpenPassword & """"
    End If
    
    permissionsList = ""
    For Each perm In Split(permissions, ",")
        permissionsList = permissionsList & """" & perm & ""","
    Next
    If permissionsList <> "" Then permissionsList = Left(permissionsList, Len(permissionsList) - 1)
    
    postData = "{" & vbCrLf & _
               "  ""assetID"": """ & assetID & """," & vbCrLf & _
               "  ""passwordProtection"": {" & vbCrLf & _
               passwordProt & vbCrLf & _
               "  }," & vbCrLf & _
               "  ""encryptionAlgorithm"": ""AES_256""," & vbCrLf & _
               "  ""contentToEncrypt"": ""ALL_CONTENT"""
    If permissionsPassword <> "" And permissionsList <> "" Then
        postData = postData & "," & vbCrLf & _
               "  ""permissions"": [" & vbCrLf & _
               permissionsList & vbCrLf & _
               "  ]"
    End If
    postData = postData & vbCrLf & _
               "}"
    'Debug.Print postData
    
    With XMLhttpRequest
        Debug.Print Time; baseURL & "operation/protectpdf"
        .Open "POST", baseURL & "operation/protectpdf", False
        .setRequestHeader "Authorization", "Bearer " & credentials.access_token
        .setRequestHeader "X-API-Key", credentials.client_id
        .setRequestHeader "Content-Type", "application/json"
        .send (postData)
        Debug.Print .status, .statusText
        'Debug.Print .responseText
        'Debug.Print .getAllResponseHeaders
        If .status <> 201 Then
            MsgBox "Protect_PDF procedure terminated because XMLhttp Protect PDF operation returned status " & .status & " " & .statusText, vbExclamation, "Protect File"
            Exit Sub
        End If
        locationUri = Split(Split(.getAllResponseHeaders, "location: ")(1), vbCrLf)(0)
    End With
    
    'https://developer.adobe.com/document-services/docs/apis/#tag/Protect-PDF/operation/pdfoperations.protectpdf.jobstatus
    '
    'Poll the protect pdf operation for completion.
    '
    'On getting 200 response code from the poll API, you will receive a status field in the response body which can either
    'be "in progress", "done" or "failed".
    'If the status field is "in progress" you need to keep polling the location until it changes to "done" or "failed".
    'If the status field is "done" the response body will also have a download pre-signed URI in the dowloadUri field, which will be used
    'to download the asset directly from cloud provider by making the following API call
    
    Do
        With XMLhttpRequest
            Debug.Print Time; locationUri
            .Open "GET", locationUri, False
            .setRequestHeader "Authorization", "Bearer " & credentials.access_token
            .setRequestHeader "X-API-Key", credentials.client_id
            .send
            Debug.Print .status, .statusText
            'Debug.Print .responseText
            If .status <> 200 Then
                MsgBox "Protect_PDF procedure terminated because XMLhttp job status request returned status " & .status & " " & .statusText, vbExclamation, "Protect PDF"
                Exit Sub
            End If
            status = GetJSONvalue(.responseText, "status")
            downloadUri = GetJSONvalue(.responseText, "downloadUri")
        End With
        Debug.Print Time; status
        If status = "in progress" Then Sleep 500
    Loop While status = "in progress"
               
    If status = "done" Then
        
        'Download the protected PDF and save it as the output PDF
        
        DownloadFile downloadUri, PDFoutputFile
        
    Else
    
        MsgBox "Protect_PDF procedure terminated because XMLhttp Protect PDF operatation failed", vbExclamation, "Protect PDF"
        Exit Sub
       
    End If
    
    'https://developer.adobe.com/document-services/docs/apis/#tag/Assets/operation/asset.delete
    'Delete the PDF from Adobe cloud storage
    
    With XMLhttpRequest
        .Open "DELETE", baseURL & "assets/" & assetID, False
        .setRequestHeader "Authorization", "Bearer " & credentials.access_token
        .setRequestHeader "X-API-Key", credentials.client_id
        .send
        Debug.Print .status, .statusText
        'Debug.Print .responseText
        'Debug.Print .getAllResponseHeaders
        If .status <> 204 Then
            MsgBox "Protect_PDF procedure terminated because XMLhttp delete asset request returned status " & .status & " " & .statusText, vbExclamation, "Protect PDF"
            Exit Sub
        End If
    End With
    
End Sub


Private Function GetJSONvalue(JSONstring As String, paramName As String) As String

    'Extract a parameter value from a JSON string

    Dim p1 As Long, p2 As Long
    
    p1 = InStr(JSONstring, Chr(34) & paramName & Chr(34))
    If p1 > 0 Then
        p1 = p1 + Len(Chr(34) & paramName & Chr(34))
        p1 = InStr(p1, JSONstring, ":") + 1
        p2 = InStr(p1, JSONstring, ",")
        If p2 = 0 Then p2 = InStr(p1, JSONstring, "]")
        If p2 = 0 Then p2 = InStr(p1, JSONstring, "}")
        GetJSONvalue = Mid(JSONstring, p1, p2 - p1)
        GetJSONvalue = Replace(GetJSONvalue, Chr(34), "")
        'Debug.Print paramName & " = " & GetJSONvalue
    End If
    
End Function


Private Function Load_File_Bytes(PDFfile As String) As Byte()

    'Read the PDF file bytes into a byte array

    Dim fileNum As Integer
    
    fileNum = FreeFile
    Open PDFfile For Binary Access Read As fileNum
    ReDim Load_File_Bytes(0 To LOF(fileNum) - 1)
    Get fileNum, , Load_File_Bytes
    Close fileNum

End Function


Public Function DownloadFile(URL As String, LocalFilename As String) As Boolean
    
    Dim retVal As Long
    
    retVal = URLDownloadToFile(0, URL, LocalFilename, 0, 0)
    If retVal = 0 Then DownloadFile = True Else DownloadFile = False

End Function


Private Function Enc(s As String) As String
    Enc = Application.WorksheetFunction.EncodeURL(s)
End Function


Private Sub Save_Credentials(credentials As AdobeCredentialsType)

    'Write credentials to cells in the specified worksheet
    
    With Credentials_Sheet
        .Range("A1:B1").Value = Array("Client ID", credentials.client_id)
        .Range("A2:B2").Value = Array("Client Secret", credentials.client_secret)
        .Range("A3:B3").Value = Array("Access Token", credentials.access_token)
        .Range("A5:B4").Value = Array("Token Type", credentials.token_type)
        .Range("A5:B5").Value = Array("Expires In (secs)", credentials.expires_in)
        .Range("A6:B6").Value = Array("Expiry Date", credentials.expiresAtDate)
        .Columns("A").AutoFit
    End With

End Sub


Private Sub Load_Credentials(ByRef credentials As AdobeCredentialsType)

    'Read credentials from cells in the specified worksheet, or prompt for Client Id and Client Secret
    
    With Credentials_Sheet
        credentials.client_id = Get_client_id(.Range("B1").Value)
        If credentials.client_id <> "" Then
            credentials.client_secret = Get_client_secret(.Range("B2").Value)
        End If
        credentials.access_token = .Range("B3").Value
        credentials.token_type = .Range("B4").Value
        credentials.expires_in = .Range("B5").Value
        credentials.expiresAtDate = .Range("B6").Value
    End With
        
End Sub


Property Get Credentials_Sheet() As Worksheet

    With ThisWorkbook
        On Error Resume Next
        Set Credentials_Sheet = .Worksheets("Credentials")
        On Error GoTo 0
        If Credentials_Sheet Is Nothing Then
            Set Credentials_Sheet = .Worksheets.Add(After:=.Worksheets(.Worksheets.Count))
            Credentials_Sheet.Name = "Credentials"
        End If
    End With

End Property


Property Get Get_client_id(client_id As String) As String
    If client_id = "" Then
        Do
            client_id = InputBox("Enter Client ID", "Adobe Credentials")
        Loop While client_id = "" And StrPtr(client_id) <> 0
    End If
    Get_client_id = client_id
End Property


Property Get Get_client_secret(client_secret As String) As String
    If client_secret = "" Then
        Do
            client_secret = InputBox("Enter Client Secret", "Adobe Credentials")
        Loop While client_secret = "" And StrPtr(client_secret) <> 0
    End If
    Get_client_secret = client_secret
End Property
 

Excel Facts

Will the fill handle fill 1, 2, 3?
Yes! Type 1 in a cell. Hold down Ctrl while you drag the fill handle.

Forum statistics

Threads
1,223,626
Messages
6,173,412
Members
452,514
Latest member
cjkelly15

We've detected that you are using an adblocker.

We have a great community of people providing Excel help here, but the hosting costs are enormous. You can help keep this site running by allowing ads on MrExcel.com.
Allow Ads at MrExcel

Which adblocker are you using?

Disable AdBlock

Follow these easy steps to disable AdBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the icon in the browser’s toolbar.
2)Click on the "Pause on this site" option.
Go back

Disable AdBlock Plus

Follow these easy steps to disable AdBlock Plus

1)Click on the icon in the browser’s toolbar.
2)Click on the toggle to disable it for "mrexcel.com".
Go back

Disable uBlock Origin

Follow these easy steps to disable uBlock Origin

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back

Disable uBlock

Follow these easy steps to disable uBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back
Back
Top