Skip to content

Commit

Permalink
#568: support for passing private_key into with_client_certificate me…
Browse files Browse the repository at this point in the history
…thod
  • Loading branch information
vgrem committed Sep 28, 2022
1 parent ae6e985 commit b644910
Show file tree
Hide file tree
Showing 12 changed files with 729 additions and 53 deletions.
48 changes: 40 additions & 8 deletions examples/sharepoint/connect_with_client_certificate.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,46 @@
import os
from office365.sharepoint.client_context import ClientContext
from tests import test_tenant, test_team_site_url
from tests import test_tenant, test_team_site_url, test_site_url

cert_settings = {
'client_id': '51d03106-4726-442c-86db-70b32fa7547f',
'thumbprint': "78CA7402E8A2508A9772CB1B2E085945147D8050",
'cert_path': '{0}/selfsigncert.pem'.format(os.path.dirname(__file__)),
#'scopes': ['{0}.default'.format(test_site_url)]
}

ctx = ClientContext(test_team_site_url).with_client_certificate(test_tenant, **cert_settings)
def create_client_default():
cert_credentials = {
'tenant': test_tenant,
'client_id': '51d03106-4726-442c-86db-70b32fa7547f',
'thumbprint': "78CA7402E8A2508A9772CB1B2E085945147D8050",
'cert_path': '{0}/selfsigncert.pem'.format(os.path.dirname(__file__)),
}
return ClientContext(test_team_site_url).with_client_certificate(**cert_credentials)


def create_client_with_scopes():
cert_credentials = {
'tenant': test_tenant,
'client_id': '51d03106-4726-442c-86db-70b32fa7547f',
'thumbprint': "78CA7402E8A2508A9772CB1B2E085945147D8050",
'cert_path': '{0}/selfsigncert.pem'.format(os.path.dirname(__file__)),
'scopes': ['{0}/.default'.format(test_site_url)]
}

return ClientContext(test_team_site_url).with_client_certificate(**cert_credentials)


def create_client_with_private_key():
cert_path = '{0}/selfsigncert.pem'.format(os.path.dirname(__file__))
with open(cert_path, 'r') as f:
private_key = open(cert_path).read()

cert_credentials = {
'tenant': test_tenant,
'client_id': '51d03106-4726-442c-86db-70b32fa7547f',
'thumbprint': "78CA7402E8A2508A9772CB1B2E085945147D8050",
'private_key': private_key
}
return ClientContext(test_team_site_url).with_client_certificate(**cert_credentials)


#ctx = create_client_default()
#ctx = create_client_with_scopes()
ctx = create_client_with_private_key()
current_web = ctx.web.get().execute_query()
print("{0}".format(current_web.url))
4 changes: 2 additions & 2 deletions generator/import_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ def export_to_file(path, content):

parser = ArgumentParser()
parser.add_argument("-e", "--endpoint", dest="endpoint",
help="Import metadata endpoint", default="sharepoint")
help="Import metadata endpoint", default="microsoftgraph")
parser.add_argument("-p", "--path",
dest="path", default="./metadata/SharePoint.xml",
dest="path", default="./metadata/MicrosoftGraph.xml",
help="Import metadata endpoint")

args = parser.parse_args()
Expand Down
353 changes: 343 additions & 10 deletions generator/metadata/MicrosoftGraph.xml

Large diffs are not rendered by default.

211 changes: 189 additions & 22 deletions generator/metadata/SharePoint.xml

Large diffs are not rendered by default.

21 changes: 14 additions & 7 deletions office365/runtime/auth/authentication_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,28 @@ def __init__(self, url):
self.url = url.rstrip("/")
self._provider = None

def with_client_certificate(self, tenant, client_id, thumbprint, cert_path, **kwargs):
def with_client_certificate(self, tenant, client_id, thumbprint, cert_path=None, private_key=None, scopes=None):
"""Creates authenticated SharePoint context via certificate credentials
:param str tenant: Tenant name, for example {}@
:param str cert_path: Path to A PEM encoded certificate private key.
:param str thumbprint: Hex encoded thumbprint of the certificate.
:param str client_id: The OAuth client id of the calling application.
:param list[str] scopes (optional): Scopes requested to access a protected API (a resource)
:param str thumbprint: Hex encoded thumbprint of the certificate.
:param str or None cert_path: Path to A PEM encoded certificate private key.
:param str or None private_key: A PEM encoded certificate private key.
:param list[str] or None scopes: Scopes requested to access a protected API (a resource)
"""
if scopes is None:
resource = get_absolute_url(self.url)
scopes = ["{url}/.default".format(url=resource)]
if cert_path is None and private_key is None:
raise ValueError("Private key is missing. Use either 'cert_path' or 'private_key' to pass the value")
elif cert_path is not None:
with open(cert_path, 'r') as f:
private_key = f.read()

def _acquire_token_for_client_certificate():
authority_url = 'https://login.microsoftonline.com/{0}'.format(tenant)
credentials = {"thumbprint": thumbprint, "private_key": open(cert_path).read()}
resource = get_absolute_url(self.url)
scopes = kwargs.get('scopes', ["{url}/.default".format(url=resource)])
credentials = {"thumbprint": thumbprint, "private_key": private_key}
import msal
app = msal.ConfidentialClientApplication(
client_id,
Expand Down
10 changes: 6 additions & 4 deletions office365/sharepoint/client_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,17 +79,19 @@ def _init_context_for_web(resp):
ctx.after_execute(_init_context_for_web)
return ctx

def with_client_certificate(self, tenant, client_id, thumbprint, cert_path, **kwargs):
def with_client_certificate(self, tenant, client_id, thumbprint, cert_path=None, private_key=None, scopes=None):
"""Creates authenticated SharePoint context via certificate credentials
:param str tenant: Tenant name
:param str cert_path: Path to A PEM encoded certificate private key.
:param str or None cert_path: Path to A PEM encoded certificate private key.
:param str or None private_key: A PEM encoded certificate private key.
:param str thumbprint: Hex encoded thumbprint of the certificate.
:param str client_id: The OAuth client id of the calling application.
:param list[str] scopes (optional): Scopes requested to access a protected API (a resource)
:param list[str] or None scopes: Scopes requested to access a protected API (a resource)
"""
self.authentication_context.with_client_certificate(tenant, client_id, thumbprint, cert_path, **kwargs)
self.authentication_context.with_client_certificate(tenant, client_id, thumbprint, cert_path, private_key,
scopes)
return self

def with_access_token(self, token_func):
Expand Down
18 changes: 18 additions & 0 deletions office365/sharepoint/translation/item_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from office365.runtime.client_value import ClientValue


class TranslationItemInfo(ClientValue):
"""The TranslationItemInfo type contains information about a previously submitted translation item."""

def __init__(self, translation_id=None):
"""
:param str translation_id: If this translation item belongs to an immediate translation job,
this property MUST be ignored. Otherwise, this property contains an identifier uniquely identifying
this translation item.
"""
super(TranslationItemInfo, self).__init__()
self.TranslationId = translation_id

@property
def entity_type_name(self):
return "SP.Translation.TranslationItemInfo"
11 changes: 11 additions & 0 deletions office365/sharepoint/translation/job.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from office365.sharepoint.base_entity import BaseEntity


class TranslationJob(BaseEntity):
"""
The TranslationJob type is used to create new translation jobs.
"""

@property
def entity_type_name(self):
return "SP.Translation.TranslationJob"
9 changes: 9 additions & 0 deletions office365/sharepoint/translation/job_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from office365.runtime.client_value import ClientValue


class TranslationJobInfo(ClientValue):
"""The TranslationJobInfo type contains information about a previously submitted translation job."""

@property
def entity_type_name(self):
return "SP.Translation.TranslationJobInfo"
5 changes: 5 additions & 0 deletions office365/sharepoint/translation/status_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ def set(self):
self.context.add_query(qry)
return return_type

def update_translation_languages(self):
qry = ServiceOperationQuery(self, "UpdateTranslationLanguages")
self.context.add_query(qry)
return self

@property
def untranslated_languages(self):
return self.properties.get("UntranslatedLanguages", StringCollection())
Expand Down
58 changes: 58 additions & 0 deletions office365/sharepoint/translation/sync_translator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
from office365.runtime.client_result import ClientResult
from office365.runtime.paths.service_operation import ServiceOperationPath
from office365.runtime.queries.service_operation import ServiceOperationQuery
from office365.sharepoint.base_entity import BaseEntity
from office365.sharepoint.translation.item_info import TranslationItemInfo


class SyncTranslator(BaseEntity):
"""
The SyncTranslator type is used to submit immediate translation jobs to the protocol server.
Status: The Machine Translations Service API will no longer be supported as of the end of July 2022.
https://go.microsoft.com/fwlink/?linkid=2187153
"""
def __init__(self, context, target_language):
"""
:param str target_language:
"""
super(SyncTranslator, self).__init__(context, ServiceOperationPath("SP.Translation.SyncTranslator",
{"targetLanguage": target_language}))

def translate(self, input_file, output_file):
"""
The protocol client calls this method to submit an immediate translation job to the protocol server.
The method returns a TranslationItemInfo object (section 3.1.5.2) that contains the results of the translation
item of the immediate translation job.
:param str input_file: This value MUST be the full or relative path to the file that contains the document
to be translated.
The file MUST be translatable. A file is considered translatable if it conforms to the constraints
enumerated in the description of the inputFile parameter of the AddFile method (section 3.1.5.3.2.1.1).
:param str output_file: This value MUST be the full or relative path to the file to where the translated
document will be stored
"""
payload = {
"inputFile": input_file,
"outputFile": output_file
}
return_type = ClientResult(self.context, TranslationItemInfo())
qry = ServiceOperationQuery(self, "Translate", None, payload)
self.context.add_query(qry)
return return_type

@property
def output_save_behavior(self):
"""
The protocol client sets this property to determine the behavior of the protocol server in the case that
the output file already exists when a translation occurs.
If the protocol client does not set this property, the AppendIfPossible (section 3.1.5.2.1.1) behavior is used.
:rtype: int or None
"""
return self.properties.get("OutputSaveBehavior", None)

@property
def entity_type_name(self):
return "SP.Translation.SyncTranslator"
34 changes: 34 additions & 0 deletions office365/sharepoint/translation/variations_timer_job.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from office365.runtime.client_value_collection import ClientValueCollection
from office365.runtime.queries.service_operation import ServiceOperationQuery
from office365.sharepoint.base_entity import BaseEntity


class VariationsTranslationTimerJob(BaseEntity):
"""
The VariationsTranslationTimerJob type provides methods to drive translation for list items in a variation label.
"""

@staticmethod
def export_items(context, list_url, item_ids, addresses_to_email):
"""
The protocol client calls this method to export a specific set of list items.
:type context: office365.sharepoint.client_context.ClientContext
:param str list_url: The server-relative URL for the list containing the list items
:param list[int] item_ids: An array containing the identifiers of the list items to be exported.
:param list[str] addresses_to_email: An array of the e-mail addresses that will be notified when the operation
completes.
"""
payload = {
"list": list_url,
"itemIds": ClientValueCollection(int, item_ids),
"addressesToEmail": ClientValueCollection(str, addresses_to_email)
}
binding_type = VariationsTranslationTimerJob(context)
qry = ServiceOperationQuery(binding_type, "ExportItems", None, payload, is_static=True)
context.add_query(qry)
return binding_type

@property
def entity_type_name(self):
return "SP.Translation.VariationsTranslationTimerJob"

0 comments on commit b644910

Please sign in to comment.