Skip to content

Commit

Permalink
directory namespace improvements & refactorings
Browse files Browse the repository at this point in the history
  • Loading branch information
vgrem committed Sep 24, 2024
1 parent 70e6fe8 commit 03330dc
Show file tree
Hide file tree
Showing 41 changed files with 463 additions and 130 deletions.
24 changes: 0 additions & 24 deletions examples/directory/applications/get_delegated_perms.py

This file was deleted.

2 changes: 1 addition & 1 deletion examples/directory/applications/grant_application_perms.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@

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


# Step 3. Print app role assignments
Expand Down
2 changes: 1 addition & 1 deletion examples/directory/applications/grant_delegated_perms.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,4 @@
# app_role = "User.Read.All"
app_role = "DeviceLocalCredential.Read.All"
user = client.users.get_by_principal_name(test_user_principal_name)
resource.grant_delegated(test_client_id, user, app_role).execute_query()
resource.grant_delegated_permissions(test_client_id, user, app_role).execute_query()
2 changes: 1 addition & 1 deletion examples/directory/applications/has_delegated_perms.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
user = client.users.get_by_principal_name(test_admin_principal_name)
client_app = client.applications.get_by_app_id(test_client_id)
# result = resource.get_delegated(client_app, user, app_role).execute_query()
result = resource.get_delegated(test_client_id, user, app_role).execute_query()
result = resource.get_delegated_permissions(test_client_id, user, app_role).execute_query()
if len(result) == 0:
print("Delegated permission '{0}' is not set".format(app_role))
else:
Expand Down
34 changes: 34 additions & 0 deletions examples/directory/applications/list_application_perms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""
How to grant and revoke delegated permissions for an app using Microsoft Graph.
Delegated permissions, also called scopes or OAuth2 permissions, allow an app to call an API
on behalf of a signed-in user.
https://learn.microsoft.com/en-us/graph/permissions-grant-via-msgraph?tabs=http&pivots=grant-delegated-permissions
"""

from office365.graph_client import GraphClient
from tests import (
test_client_id,
test_tenant,
test_client_secret,
)

# client = GraphClient.with_token_interactive(
# test_tenant, test_client_id, test_admin_principal_name
# )

client = GraphClient.with_client_secret(test_tenant, test_client_id, test_client_secret)


resource = (
client.service_principals.get_by_name("Microsoft Graph")
)

result = resource.get_application_permissions(test_client_id).execute_query()
for app_role in result.value:
print(app_role)




18 changes: 9 additions & 9 deletions examples/directory/applications/list_delegated_perms.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,20 @@

from office365.graph_client import GraphClient
from tests import (
test_admin_principal_name,
test_client_id,
test_tenant,
test_user_principal_name,
test_client_secret,
)

client = GraphClient.with_token_interactive(
test_tenant, test_client_id, test_admin_principal_name
)
#client = GraphClient.with_token_interactive(
# test_tenant, test_client_id, test_admin_principal_name
#)

client = GraphClient.with_client_secret(test_tenant, test_client_id, test_client_secret)


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

for grant in result:
print(grant)
print(grant.scope)
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,4 @@

# Get resource
resource = client.service_principals.get_by_name("Microsoft Graph")
resource.revoke_application(test_client_id, "MailboxSettings.Read").execute_query()
resource.revoke_application_permissions(test_client_id, "MailboxSettings.Read").execute_query()
2 changes: 1 addition & 1 deletion examples/directory/applications/revoke_delegated_perms.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@
# Step 1: Get resource service principal
resource = client.service_principals.get_by_name("Microsoft Graph")
user = client.users.get_by_principal_name(test_user_principal_name)
resource.revoke_delegated(test_client_id, user, "User.Read.All").execute_query()
resource.revoke_delegated_permissions(test_client_id, user, "User.Read.All").execute_query()
5 changes: 2 additions & 3 deletions examples/security/run_hunting_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@

client = GraphClient.with_client_secret(test_tenant, test_client_id, test_client_secret)
query = """
DeviceProcessEvents | where InitiatingProcessFileName =~ \"powershell.exe\"
| project Timestamp, FileName, InitiatingProcessFileName
| order by Timestamp desc | limit 2"""
DeviceProcessEvents | where InitiatingProcessFileName =~ \"powershell.exe\" | project Timestamp, FileName, \
InitiatingProcessFileName | order by Timestamp desc | limit 2"""
result = client.security.run_hunting_query(query).execute_query()
print(result.value)
34 changes: 0 additions & 34 deletions examples/sharepoint/__init__.py
Original file line number Diff line number Diff line change
@@ -1,37 +1,3 @@
from office365.sharepoint.fields.creation_information import FieldCreationInformation
from office365.sharepoint.fields.type import FieldType
from office365.sharepoint.lists.creation_information import ListCreationInformation
from office365.sharepoint.lists.list import List
from office365.sharepoint.lists.template_type import ListTemplateType
from office365.sharepoint.webs.web import Web
from tests import create_unique_name


def upload_sample_file(context, path):
"""
:type context: office365.sharepoint.client_context.ClientContext
:type path: str
"""
folder = context.web.default_document_library().root_folder
with open(path, "rb") as f:
file = folder.files.upload(f).execute_query()
return file


def create_sample_tasks_list(web):
# type: (Web) -> List
list_title = "Company Tasks"

list_title = create_unique_name("Tasks N")
list_create_info = ListCreationInformation(
list_title, None, ListTemplateType.TasksWithTimelineAndHierarchy
)

return_type = web.lists.add(list_create_info).execute_query()
field_info = FieldCreationInformation("Manager", FieldType.User)
return_type.fields.add(field_info).execute_query()
return return_type


def configure():
pass
7 changes: 5 additions & 2 deletions examples/sharepoint/sharing/share_file.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import json

from examples.sharepoint import upload_sample_file
from office365.sharepoint.client_context import ClientContext
from office365.sharepoint.sharing.links.kind import SharingLinkKind
from office365.sharepoint.webs.web import Web
Expand All @@ -18,7 +17,11 @@

ctx = ClientContext(test_team_site_url).with_credentials(test_user_credentials)

remote_file = upload_sample_file(ctx, "../../data/SharePoint User Guide.docx")
local_path = "../../data/SharePoint User Guide.docx"
lib = ctx.web.default_document_library()
with open(local_path, "rb") as f:
remote_file = lib.root_folder.files.upload(f).execute_query()


print("Creating a sharing link for a file...")
result = remote_file.share_link(SharingLinkKind.AnonymousView).execute_query()
Expand Down
4 changes: 2 additions & 2 deletions generator/import_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,13 @@ def export_to_file(path, content):
"--endpoint",
dest="endpoint",
help="Import metadata endpoint",
default="sharepoint",
default="graph",
)
parser.add_argument(
"-p",
"--path",
dest="path",
default="./metadata/SharePoint.xml",
default="./metadata/MicrosoftGraph.xml",
help="Import metadata endpoint",
)

Expand Down
4 changes: 2 additions & 2 deletions generator/metadata/MicrosoftGraph.xml
Original file line number Diff line number Diff line change
Expand Up @@ -42522,9 +42522,9 @@
<Property Name="callerNumber" Type="Edm.String"/>
<Property Name="callId" Type="Edm.String"/>
<Property Name="callType" Type="Edm.String"/>
<Property Name="charge" Type="Edm.Decimal" Scale="Variable"/>
<Property Name="charge" Type="Edm.Decimal" Scale="variable"/>
<Property Name="conferenceId" Type="Edm.String"/>
<Property Name="connectionCharge" Type="Edm.Decimal" Scale="Variable"/>
<Property Name="connectionCharge" Type="Edm.Decimal" Scale="variable"/>
<Property Name="currency" Type="Edm.String"/>
<Property Name="destinationContext" Type="Edm.String"/>
<Property Name="destinationName" Type="Edm.String"/>
Expand Down
39 changes: 39 additions & 0 deletions office365/communications/callrecords/collection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from datetime import datetime, timedelta

from office365.communications.callrecords.call_record import CallRecord
from office365.communications.callrecords.direct_routing_log_row import (
DirectRoutingLogRow,
)
from office365.entity_collection import EntityCollection
from office365.runtime.client_result import ClientResult
from office365.runtime.client_value_collection import ClientValueCollection
from office365.runtime.queries.function import FunctionQuery


class CallRecordCollection(EntityCollection[CallRecord]):
def __init__(self, context, resource_path=None):
super(CallRecordCollection, self).__init__(context, CallRecord, resource_path)

def get_direct_routing_calls(self, from_datetime=None, to_datetime=None):
"""
Get a log of direct routing calls as a collection of directRoutingLogRow entries.
:param datetime from_datetime: Start of time range to query.
:param datetime to_datetime: End of time range to query
"""
if to_datetime is None:
to_datetime = datetime.now()

if from_datetime is None:
from_datetime = to_datetime - timedelta(days=30)

return_type = ClientResult(
self.context, ClientValueCollection(DirectRoutingLogRow)
)
payload = {"fromDateTime": from_datetime.strftime('%Y-%m-%d'), "toDateTime": to_datetime.strftime('%Y-%m-%d')}
qry = FunctionQuery(self, "getDirectRoutingCalls", payload, return_type)

def _patch_request(request):
request.url = request.url.replace("'", "")

self.context.add_query(qry).before_query_execute(_patch_request)
return return_type
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from office365.runtime.client_value import ClientValue


class DirectRoutingLogRow(ClientValue):
"""Represents a row of data in the direct routing call log. Each row maps to one call."""
5 changes: 2 additions & 3 deletions office365/communications/cloud_communications.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from office365.communications.callrecords.call_record import CallRecord
from office365.communications.callrecords.collection import CallRecordCollection
from office365.communications.calls.collection import CallCollection
from office365.communications.onlinemeetings.collection import OnlineMeetingCollection
from office365.communications.presences.presence import Presence
Expand Down Expand Up @@ -37,9 +37,8 @@ def call_records(self):
""" " """
return self.properties.get(
"callRecords",
EntityCollection(
CallRecordCollection(
self.context,
CallRecord,
ResourcePath("callRecords", self.resource_path),
),
)
Expand Down
16 changes: 15 additions & 1 deletion office365/directory/applications/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
from office365.directory.applications.api import ApiApplication
from office365.directory.applications.optional_claims import OptionalClaims
from office365.directory.applications.public_client import PublicClientApplication
from office365.directory.applications.required_resource_access import (
RequiredResourceAccess,
)
from office365.directory.applications.roles.role import AppRole
from office365.directory.applications.spa import SpaApplication
from office365.directory.certificates.certification import Certification
Expand Down Expand Up @@ -53,7 +56,7 @@ def add_certificate(
:param datetime.datetime end_datetime: The date and time at which the credential expires. Default: now + 180days
"""
if start_datetime is None:
start_datetime = datetime.datetime.utcnow()
start_datetime = datetime.datetime.now()
if end_datetime is None:
end_datetime = start_datetime + datetime.timedelta(days=180)

Expand Down Expand Up @@ -315,6 +318,16 @@ def extension_properties(self):
),
)

@property
def required_resource_access(self):
"""Specifies the resources that the application needs to access. This property also specifies the set
of delegated permissions and application roles that it needs for each of those resources.
This configuration of access to the required resources drives the consent experience.
"""
return self.properties.get(
"requiredResourceAccess", ClientValueCollection(RequiredResourceAccess)
)

@property
def token_issuance_policies(self):
# type: () -> EntityCollection[TokenIssuancePolicy]
Expand All @@ -339,6 +352,7 @@ def get_property(self, name, default_value=None):
"optionalClaims": self.optional_claims,
"passwordCredentials": self.password_credentials,
"publicClient": self.public_client,
"requiredResourceAccess": self.required_resource_access,
"tokenIssuancePolicies": self.token_issuance_policies,
}
default_value = property_mapping.get(name, None)
Expand Down
24 changes: 24 additions & 0 deletions office365/directory/applications/required_resource_access.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from office365.directory.applications.resource_access import ResourceAccess
from office365.runtime.client_value import ClientValue
from office365.runtime.client_value_collection import ClientValueCollection


class RequiredResourceAccess(ClientValue):
"""Specifies the set of OAuth 2.0 permission scopes and app roles under the specified resource that an
application requires access to. The application may request the specified OAuth 2.0 permission scopes or app
roles through the requiredResourceAccess property, which is a collection of requiredResourceAccess objects.
"""

def __init__(self, resource_access=None, resource_app_id=None):
"""
:param list[ResourceAccess] resource_access: The list of OAuth2.0 permission scopes and app roles that
the application requires from the specified resource.
:param str resource_app_id: The unique identifier for the resource that the application requires access to.
This should be equal to the appId declared on the target resource application.
"""
self.resourceAccess = ClientValueCollection(ResourceAccess, resource_access)
self.resourceAppId = resource_app_id


def __repr__(self):
return self.resourceAppId or self.entity_type_name
26 changes: 26 additions & 0 deletions office365/directory/applications/resource_access.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from office365.runtime.client_value import ClientValue


class ResourceAccess(ClientValue):
""" Object used to specify an OAuth 2.0 permission scope or an app role that an application requires,
through the resourceAccess property of the requiredResourceAccess resource type. """

def __init__(self, id_=None, type_=None):
"""
:param str id_: The unique identifier of an app role or delegated permission exposed by the resource
application. For delegated permissions, this should match the id property of one of the delegated
permissions in the oauth2PermissionScopes collection of the resource application's service principal.
For app roles (application permissions), this should match the id property of an app role in the appRoles
collection of the resource application's service principal.
:param str type_: Specifies whether the id property references a delegated permission or an app role
(application permission). The possible values are: Scope (for delegated permissions) or Role (for app roles).
"""
self.id = id_
self.type = type_

def __repr__(self):
return "ResourceAccess(id={!r}, type={!r})".format(self.id, self.type)

@property
def type_name(self):
return "Delegated" if self.type == "Scope" else "Application"
Loading

0 comments on commit 03330dc

Please sign in to comment.