Skip to content

Commit 03330dc

Browse files
committed
directory namespace improvements & refactorings
1 parent 70e6fe8 commit 03330dc

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+463
-130
lines changed

examples/directory/applications/get_delegated_perms.py

-24
This file was deleted.

examples/directory/applications/grant_application_perms.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030

3131
# Step 2: Grant an app role to a client app
3232
app = client.applications.get_by_app_id(test_client_id)
33-
resource.grant_application(app, "MailboxSettings.Read").execute_query()
33+
resource.grant_application_permissions(app, "MailboxSettings.Read").execute_query()
3434

3535

3636
# Step 3. Print app role assignments

examples/directory/applications/grant_delegated_perms.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,4 @@
2424
# app_role = "User.Read.All"
2525
app_role = "DeviceLocalCredential.Read.All"
2626
user = client.users.get_by_principal_name(test_user_principal_name)
27-
resource.grant_delegated(test_client_id, user, app_role).execute_query()
27+
resource.grant_delegated_permissions(test_client_id, user, app_role).execute_query()

examples/directory/applications/has_delegated_perms.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
user = client.users.get_by_principal_name(test_admin_principal_name)
2525
client_app = client.applications.get_by_app_id(test_client_id)
2626
# result = resource.get_delegated(client_app, user, app_role).execute_query()
27-
result = resource.get_delegated(test_client_id, user, app_role).execute_query()
27+
result = resource.get_delegated_permissions(test_client_id, user, app_role).execute_query()
2828
if len(result) == 0:
2929
print("Delegated permission '{0}' is not set".format(app_role))
3030
else:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
"""
2+
How to grant and revoke delegated permissions for an app using Microsoft Graph.
3+
Delegated permissions, also called scopes or OAuth2 permissions, allow an app to call an API
4+
on behalf of a signed-in user.
5+
6+
7+
https://learn.microsoft.com/en-us/graph/permissions-grant-via-msgraph?tabs=http&pivots=grant-delegated-permissions
8+
"""
9+
10+
from office365.graph_client import GraphClient
11+
from tests import (
12+
test_client_id,
13+
test_tenant,
14+
test_client_secret,
15+
)
16+
17+
# client = GraphClient.with_token_interactive(
18+
# test_tenant, test_client_id, test_admin_principal_name
19+
# )
20+
21+
client = GraphClient.with_client_secret(test_tenant, test_client_id, test_client_secret)
22+
23+
24+
resource = (
25+
client.service_principals.get_by_name("Microsoft Graph")
26+
)
27+
28+
result = resource.get_application_permissions(test_client_id).execute_query()
29+
for app_role in result.value:
30+
print(app_role)
31+
32+
33+
34+

examples/directory/applications/list_delegated_perms.py

+9-9
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,20 @@
99

1010
from office365.graph_client import GraphClient
1111
from tests import (
12-
test_admin_principal_name,
1312
test_client_id,
1413
test_tenant,
15-
test_user_principal_name,
14+
test_client_secret,
1615
)
1716

18-
client = GraphClient.with_token_interactive(
19-
test_tenant, test_client_id, test_admin_principal_name
20-
)
17+
#client = GraphClient.with_token_interactive(
18+
# test_tenant, test_client_id, test_admin_principal_name
19+
#)
20+
21+
client = GraphClient.with_client_secret(test_tenant, test_client_id, test_client_secret)
2122

2223

23-
resource = client.service_principals.get_by_name("Microsoft Graph")
24-
user = client.users.get_by_principal_name(test_user_principal_name)
25-
result = resource.get_delegated(test_client_id, user).execute_query()
24+
resource = client.service_principals.get_by_name("Microsoft Graph").get().execute_query()
25+
result = resource.get_delegated_permissions(test_client_id, only_admin_consent=True).execute_query()
2626

2727
for grant in result:
28-
print(grant)
28+
print(grant.scope)

examples/directory/applications/revoke_application_perms.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,4 @@
2424

2525
# Get resource
2626
resource = client.service_principals.get_by_name("Microsoft Graph")
27-
resource.revoke_application(test_client_id, "MailboxSettings.Read").execute_query()
27+
resource.revoke_application_permissions(test_client_id, "MailboxSettings.Read").execute_query()

examples/directory/applications/revoke_delegated_perms.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,4 @@
1919
# Step 1: Get resource service principal
2020
resource = client.service_principals.get_by_name("Microsoft Graph")
2121
user = client.users.get_by_principal_name(test_user_principal_name)
22-
resource.revoke_delegated(test_client_id, user, "User.Read.All").execute_query()
22+
resource.revoke_delegated_permissions(test_client_id, user, "User.Read.All").execute_query()

examples/security/run_hunting_query.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@
1414

1515
client = GraphClient.with_client_secret(test_tenant, test_client_id, test_client_secret)
1616
query = """
17-
DeviceProcessEvents | where InitiatingProcessFileName =~ \"powershell.exe\"
18-
| project Timestamp, FileName, InitiatingProcessFileName
19-
| order by Timestamp desc | limit 2"""
17+
DeviceProcessEvents | where InitiatingProcessFileName =~ \"powershell.exe\" | project Timestamp, FileName, \
18+
InitiatingProcessFileName | order by Timestamp desc | limit 2"""
2019
result = client.security.run_hunting_query(query).execute_query()
2120
print(result.value)

examples/sharepoint/__init__.py

-34
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,3 @@
1-
from office365.sharepoint.fields.creation_information import FieldCreationInformation
2-
from office365.sharepoint.fields.type import FieldType
3-
from office365.sharepoint.lists.creation_information import ListCreationInformation
4-
from office365.sharepoint.lists.list import List
5-
from office365.sharepoint.lists.template_type import ListTemplateType
6-
from office365.sharepoint.webs.web import Web
7-
from tests import create_unique_name
81

92

10-
def upload_sample_file(context, path):
11-
"""
12-
:type context: office365.sharepoint.client_context.ClientContext
13-
:type path: str
14-
"""
15-
folder = context.web.default_document_library().root_folder
16-
with open(path, "rb") as f:
17-
file = folder.files.upload(f).execute_query()
18-
return file
193

20-
21-
def create_sample_tasks_list(web):
22-
# type: (Web) -> List
23-
list_title = "Company Tasks"
24-
25-
list_title = create_unique_name("Tasks N")
26-
list_create_info = ListCreationInformation(
27-
list_title, None, ListTemplateType.TasksWithTimelineAndHierarchy
28-
)
29-
30-
return_type = web.lists.add(list_create_info).execute_query()
31-
field_info = FieldCreationInformation("Manager", FieldType.User)
32-
return_type.fields.add(field_info).execute_query()
33-
return return_type
34-
35-
36-
def configure():
37-
pass

examples/sharepoint/sharing/share_file.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import json
22

3-
from examples.sharepoint import upload_sample_file
43
from office365.sharepoint.client_context import ClientContext
54
from office365.sharepoint.sharing.links.kind import SharingLinkKind
65
from office365.sharepoint.webs.web import Web
@@ -18,7 +17,11 @@
1817

1918
ctx = ClientContext(test_team_site_url).with_credentials(test_user_credentials)
2019

21-
remote_file = upload_sample_file(ctx, "../../data/SharePoint User Guide.docx")
20+
local_path = "../../data/SharePoint User Guide.docx"
21+
lib = ctx.web.default_document_library()
22+
with open(local_path, "rb") as f:
23+
remote_file = lib.root_folder.files.upload(f).execute_query()
24+
2225

2326
print("Creating a sharing link for a file...")
2427
result = remote_file.share_link(SharingLinkKind.AnonymousView).execute_query()

generator/import_metadata.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,13 @@ def export_to_file(path, content):
2626
"--endpoint",
2727
dest="endpoint",
2828
help="Import metadata endpoint",
29-
default="sharepoint",
29+
default="graph",
3030
)
3131
parser.add_argument(
3232
"-p",
3333
"--path",
3434
dest="path",
35-
default="./metadata/SharePoint.xml",
35+
default="./metadata/MicrosoftGraph.xml",
3636
help="Import metadata endpoint",
3737
)
3838

generator/metadata/MicrosoftGraph.xml

+2-2
Original file line numberDiff line numberDiff line change
@@ -42522,9 +42522,9 @@
4252242522
<Property Name="callerNumber" Type="Edm.String"/>
4252342523
<Property Name="callId" Type="Edm.String"/>
4252442524
<Property Name="callType" Type="Edm.String"/>
42525-
<Property Name="charge" Type="Edm.Decimal" Scale="Variable"/>
42525+
<Property Name="charge" Type="Edm.Decimal" Scale="variable"/>
4252642526
<Property Name="conferenceId" Type="Edm.String"/>
42527-
<Property Name="connectionCharge" Type="Edm.Decimal" Scale="Variable"/>
42527+
<Property Name="connectionCharge" Type="Edm.Decimal" Scale="variable"/>
4252842528
<Property Name="currency" Type="Edm.String"/>
4252942529
<Property Name="destinationContext" Type="Edm.String"/>
4253042530
<Property Name="destinationName" Type="Edm.String"/>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
from datetime import datetime, timedelta
2+
3+
from office365.communications.callrecords.call_record import CallRecord
4+
from office365.communications.callrecords.direct_routing_log_row import (
5+
DirectRoutingLogRow,
6+
)
7+
from office365.entity_collection import EntityCollection
8+
from office365.runtime.client_result import ClientResult
9+
from office365.runtime.client_value_collection import ClientValueCollection
10+
from office365.runtime.queries.function import FunctionQuery
11+
12+
13+
class CallRecordCollection(EntityCollection[CallRecord]):
14+
def __init__(self, context, resource_path=None):
15+
super(CallRecordCollection, self).__init__(context, CallRecord, resource_path)
16+
17+
def get_direct_routing_calls(self, from_datetime=None, to_datetime=None):
18+
"""
19+
Get a log of direct routing calls as a collection of directRoutingLogRow entries.
20+
:param datetime from_datetime: Start of time range to query.
21+
:param datetime to_datetime: End of time range to query
22+
"""
23+
if to_datetime is None:
24+
to_datetime = datetime.now()
25+
26+
if from_datetime is None:
27+
from_datetime = to_datetime - timedelta(days=30)
28+
29+
return_type = ClientResult(
30+
self.context, ClientValueCollection(DirectRoutingLogRow)
31+
)
32+
payload = {"fromDateTime": from_datetime.strftime('%Y-%m-%d'), "toDateTime": to_datetime.strftime('%Y-%m-%d')}
33+
qry = FunctionQuery(self, "getDirectRoutingCalls", payload, return_type)
34+
35+
def _patch_request(request):
36+
request.url = request.url.replace("'", "")
37+
38+
self.context.add_query(qry).before_query_execute(_patch_request)
39+
return return_type
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from office365.runtime.client_value import ClientValue
2+
3+
4+
class DirectRoutingLogRow(ClientValue):
5+
"""Represents a row of data in the direct routing call log. Each row maps to one call."""

office365/communications/cloud_communications.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from office365.communications.callrecords.call_record import CallRecord
1+
from office365.communications.callrecords.collection import CallRecordCollection
22
from office365.communications.calls.collection import CallCollection
33
from office365.communications.onlinemeetings.collection import OnlineMeetingCollection
44
from office365.communications.presences.presence import Presence
@@ -37,9 +37,8 @@ def call_records(self):
3737
""" " """
3838
return self.properties.get(
3939
"callRecords",
40-
EntityCollection(
40+
CallRecordCollection(
4141
self.context,
42-
CallRecord,
4342
ResourcePath("callRecords", self.resource_path),
4443
),
4544
)

office365/directory/applications/application.py

+15-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
from office365.directory.applications.api import ApiApplication
66
from office365.directory.applications.optional_claims import OptionalClaims
77
from office365.directory.applications.public_client import PublicClientApplication
8+
from office365.directory.applications.required_resource_access import (
9+
RequiredResourceAccess,
10+
)
811
from office365.directory.applications.roles.role import AppRole
912
from office365.directory.applications.spa import SpaApplication
1013
from office365.directory.certificates.certification import Certification
@@ -53,7 +56,7 @@ def add_certificate(
5356
:param datetime.datetime end_datetime: The date and time at which the credential expires. Default: now + 180days
5457
"""
5558
if start_datetime is None:
56-
start_datetime = datetime.datetime.utcnow()
59+
start_datetime = datetime.datetime.now()
5760
if end_datetime is None:
5861
end_datetime = start_datetime + datetime.timedelta(days=180)
5962

@@ -315,6 +318,16 @@ def extension_properties(self):
315318
),
316319
)
317320

321+
@property
322+
def required_resource_access(self):
323+
"""Specifies the resources that the application needs to access. This property also specifies the set
324+
of delegated permissions and application roles that it needs for each of those resources.
325+
This configuration of access to the required resources drives the consent experience.
326+
"""
327+
return self.properties.get(
328+
"requiredResourceAccess", ClientValueCollection(RequiredResourceAccess)
329+
)
330+
318331
@property
319332
def token_issuance_policies(self):
320333
# type: () -> EntityCollection[TokenIssuancePolicy]
@@ -339,6 +352,7 @@ def get_property(self, name, default_value=None):
339352
"optionalClaims": self.optional_claims,
340353
"passwordCredentials": self.password_credentials,
341354
"publicClient": self.public_client,
355+
"requiredResourceAccess": self.required_resource_access,
342356
"tokenIssuancePolicies": self.token_issuance_policies,
343357
}
344358
default_value = property_mapping.get(name, None)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from office365.directory.applications.resource_access import ResourceAccess
2+
from office365.runtime.client_value import ClientValue
3+
from office365.runtime.client_value_collection import ClientValueCollection
4+
5+
6+
class RequiredResourceAccess(ClientValue):
7+
"""Specifies the set of OAuth 2.0 permission scopes and app roles under the specified resource that an
8+
application requires access to. The application may request the specified OAuth 2.0 permission scopes or app
9+
roles through the requiredResourceAccess property, which is a collection of requiredResourceAccess objects.
10+
"""
11+
12+
def __init__(self, resource_access=None, resource_app_id=None):
13+
"""
14+
:param list[ResourceAccess] resource_access: The list of OAuth2.0 permission scopes and app roles that
15+
the application requires from the specified resource.
16+
:param str resource_app_id: The unique identifier for the resource that the application requires access to.
17+
This should be equal to the appId declared on the target resource application.
18+
"""
19+
self.resourceAccess = ClientValueCollection(ResourceAccess, resource_access)
20+
self.resourceAppId = resource_app_id
21+
22+
23+
def __repr__(self):
24+
return self.resourceAppId or self.entity_type_name
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from office365.runtime.client_value import ClientValue
2+
3+
4+
class ResourceAccess(ClientValue):
5+
""" Object used to specify an OAuth 2.0 permission scope or an app role that an application requires,
6+
through the resourceAccess property of the requiredResourceAccess resource type. """
7+
8+
def __init__(self, id_=None, type_=None):
9+
"""
10+
:param str id_: The unique identifier of an app role or delegated permission exposed by the resource
11+
application. For delegated permissions, this should match the id property of one of the delegated
12+
permissions in the oauth2PermissionScopes collection of the resource application's service principal.
13+
For app roles (application permissions), this should match the id property of an app role in the appRoles
14+
collection of the resource application's service principal.
15+
:param str type_: Specifies whether the id property references a delegated permission or an app role
16+
(application permission). The possible values are: Scope (for delegated permissions) or Role (for app roles).
17+
"""
18+
self.id = id_
19+
self.type = type_
20+
21+
def __repr__(self):
22+
return "ResourceAccess(id={!r}, type={!r})".format(self.id, self.type)
23+
24+
@property
25+
def type_name(self):
26+
return "Delegated" if self.type == "Scope" else "Application"

0 commit comments

Comments
 (0)