shareplum icon indicating copy to clipboard operation
shareplum copied to clipboard

Authenticate as a Service/Daemon with a Certificate OR plugable authentication

Open daniel-butler opened this issue 3 years ago • 4 comments

I am loving the design perspective of this package. It's much cleaner than other solutions I have been looking at using. The problem I've ran into is authenticating as a Daemon/Service.

Using the msal library it is possible to authenticate into Office365 sharepoint site using a certificate. What is returned is the Bearer Token.

What would be the steps to use a token when making requests?

I tried implementing the following to get the authorization cookies to see if that would work.

import msal
import requests
from requests.cookies import RequestsCookieJar

tenant = '' # fill in
sharepoint_api_certificate_thumbprint = '' # fill in
sharepoint_api_private_file_location = '' # fill in
sharepoint_api_key = '' # fill in
sharepoint_base_url = '' # fill in


def client_certificate_auth() -> dict:
    """Get the Token with client secret

    https://github.com/Azure-Samples/ms-identity-python-daemon/tree/master/1-Call-MsGraph-WithSecret
    """
    scope = ["https://graph.microsoft.com/.default"]
    private_key_details = {
        'thumbprint': sharepoint_api_certificate_thumbprint,
        'private_key': open(sharepoint_api_private_file_location).read(),
    }
    app = msal.ConfidentialClientApplication(
        authority=f"https://login.microsoftonline.com/{tenant}",
        client_id=sharepoint_api_key,
        client_credential=private_key_details,
    )

    result = app.acquire_token_silent(scopes=scope, account=None)

    if not result:
        result = app.acquire_token_for_client(scopes=scope)

    return result


def get_cookies(access_header: dict) -> RequestsCookieJar:
    url = f'{sharepoint_base_url}/_forms/default.aspx?wa=wsignin1.0'
    response = requests.post(url, access_header['access_token'])
    response.raise_for_status()
    return response.cookies

Using the functions above I tried to get the cookies and plug those into the library

from shareplum import Office365, Site
from shareplum.site import Version

# from above
access_header = client_certificate_auth()
authcookie = get_cookies(access_header)


file_name = '' # fill in
file_folder_name = '' # fill in

sharepoint = Site(f"{sharepoint_base_url }/sites/{tenant}", version=Version.v2016, authcookie=authcookie)
folder = sharepoint .Folder(file_folder_name)
file = folder.get_file(file_name)

The error returned (with tenant, site, and file locations redacted

Traceback (most recent call last):
  File "...\lib\site-packages\shareplum\request_helper.py", line 17, in post
    response.raise_for_status()
  File "...\lib\site-packages\requests\models.py", line 941, in raise_for_status
    raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 403 Client Error: Forbidden for url: https://{tenant}.sharepoint.com/sites/{site}/_vti_bin/lists.asmx
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "...\lib\site-packages\shareplum\site.py", line 547, in Site
    return _Site365(site_url,
  File "...\lib\site-packages\shareplum\site.py", line 404, in __init__
    super().__init__(site_url, auth, authcookie, verify_ssl, ssl_version, huge_tree, timeout)
  File "...\lib\site-packages\shareplum\site.py", line 96, in __init__
    self.users = self.get_users()
  File "...\lib\site-packages\shareplum\site.py", line 339, in get_users
    response = post(self._session,
  File "...\lib\site-packages\shareplum\request_helper.py", line 20, in post
    raise ShareplumRequestError("Shareplum HTTP Post Failed", err)
shareplum.errors.ShareplumRequestError: Shareplum HTTP Post Failed : 403 Client Error: Forbidden for url: https://{tenant}.sharepoint.com/sites/{site}/_vti_bin/lists.asmx

daniel-butler avatar Sep 17 '20 14:09 daniel-butler

I'm not really familiar with SharePoint authentication to be honest. This is a bit beyond my understanding. I don't think I can help.

jasonrollins avatar Oct 08 '20 02:10 jasonrollins

Did this go anywhere? any code written or PR? I'm surprised there isnt more interest in service account auth?

Its something I'm interested in and can potentially submit a PR if it hasnt already been done

laurieodgers avatar Jan 17 '22 08:01 laurieodgers

Hi @daniel-butler did you find any solution to this error?

wasay-10pearls avatar Sep 23 '22 05:09 wasay-10pearls

No, I couldn't get it to work. I instead used Office365-REST-Python-Client with the app certificate authentication flow. Here is the documentation from Microsoft. I tried to get the authentication headers using this flow to use share plum instead, but wasn't able to get it to work. After a while, I gave up and implemented the code below.

pip install Office365-REST-Python-Client

Below is the code.

from office365.sharepoint.client_context import ClientContext

...

ctx = ClientContext.connect_with_certificate(
    base_url=f'{settings._base_url}/sites/XXXXX',
    client_id=settings._api_key,
    thumbprint=settings._api_certificate_thumbprint,
    cert_path=settings._api_private_file_location,
)
target_folder = ctx.web.get_folder_by_server_relative_url(remote)
target_folder.upload_file(local.name, file_content)
ctx.execute_query()

Not sure why I didn't see the earlier messages. I would have said something sooner!

daniel-butler avatar Sep 23 '22 12:09 daniel-butler