Skip to content

Terms of use - list agreements and track user acceptances.

Shows the tenant's terms-of-use agreements, the file behind each one, and how many users have accepted. The final section highlights users who have not yet accepted any agreement.

Reference:

Permissions

Requires application permissions Agreement.Read.All and AgreementAcceptance.Read.All (plus User.Read.All for the pending list). Not every tenant grants them; the script reports a denied request and exits 0.

Reference

View source

from __future__ import annotations

import argparse

from office365.graph_client import GraphClient
from office365.runtime.client_request_exception import ClientRequestException
from tests.settings import client_id, client_secret, tenant


def _client() -> GraphClient:
    return GraphClient(tenant=tenant).with_client_secret(client_id, client_secret)


def main() -> None:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--users", type=int, default=100, help="max users to check for missing acceptance")
    args = parser.parse_args()

    client = _client()
    terms_of_use = client.identity_governance.terms_of_use

    try:
        agreements = terms_of_use.agreements.get().execute_query()
    except ClientRequestException as e:
        print(f"Terms of use are not available with these credentials:\n  {e}")
        return

    print(f"Terms of use agreements ({len(agreements)}):\n")
    for agreement in agreements:
        print(f"  {agreement.display_name or '(unnamed)'}")
        print(
            f"    view_required={agreement.is_viewing_before_acceptance_required}  "
            f"per_device={agreement.is_per_device_acceptance_required}  "
            f"reaccept_every={agreement.user_reaccept_required_frequency}"
        )
        for file in agreement.files.get().execute_query():
            print(f"    file: {file.file_name or file.display_name or '?'}")

        accepted = {a.user_principal_name for a in agreement.acceptances.get().execute_query()}
        accepted.discard(None)
        print(f"    accepted by {len(accepted)} user(s)")
        for upn in sorted(accepted)[:5]:
            print(f"      {upn}")

    all_acceptances = terms_of_use.agreement_acceptances.get().execute_query()
    accepted_upns = {a.user_principal_name.upper() for a in all_acceptances if a.user_principal_name}
    print(f"\nTotal acceptances: {len(all_acceptances)}")

    users = client.users.select(["userPrincipalName", "displayName"]).top(args.users).get().execute_query()
    pending = [u for u in users if (u.user_principal_name or "").upper() not in accepted_upns]
    if pending:
        print(f"\nUsers without acceptance: {len(pending)} / {len(users)}")
        for user in pending[:5]:
            print(f"  {user.user_principal_name}  {user.display_name or ''}")


if __name__ == "__main__":
    main()

← Back to Microsoft Entra ID Governance