jborean93 / pypsrp

PowerShell Remoting Protocol for Python
MIT License
328 stars 49 forks source link

Cannot connect to Exchange Online using OAuth #183

Open filipvh opened 11 months ago

filipvh commented 11 months ago

I used the example provided in the readme MSAL seems to work fine, but I get a 400 error with no other details.

The code I've used (partially modified):


import hashlib

import msal
import psrp

from cryptography.hazmat.primitives.serialization import (
    Encoding,
    NoEncryption,
    PrivateFormat,
)
from cryptography.hazmat.primitives.serialization.pkcs12 import (
    load_key_and_certificates,
)

def get_msal_token(
        organization: str,
        client_id: str,
        pfx_path: str,
        pfx_password: str | None,
) -> str:
    pfx = open(pfx_path, mode="rb").read()
    print(pfx)
    private_key, main_cert, add_certs = load_key_and_certificates(
        pfx,
        pfx_password.encode("utf-8") if pfx_password else None,
        None
    )
    assert private_key is not None
    assert main_cert is not None
    key = private_key.private_bytes(
        Encoding.PEM,
        PrivateFormat.PKCS8,
        NoEncryption(),
    ).decode()

    cert_thumbprint = hashlib.sha1()
    cert_thumbprint.update(main_cert.public_bytes(Encoding.DER))

    app = msal.ConfidentialClientApplication(
        authority=f"https://login.microsoftonline.com/{organization}",
        client_id=client_id,
        client_credential={
            "private_key": key,
            "thumbprint": cert_thumbprint.hexdigest().upper(),
        },
    )

    result = app.acquire_token_for_client(scopes=[
        "https://outlook.office365.com/.default"
    ])
    if err := result.get("error", None):
        msg = f"Failed to get MSAL token {err} - {result['error_description']}"
        raise Exception(msg)

    return f"{result['token_type']} {result['access_token']}"

def main() -> None:
    tenant_id = "214e89fc-27c2-4663-8683-210792b6496a"

    # This is the ID of the Application Role to authenticate as
    client_id = "my-client-id" #removed :p

    msal_token = get_msal_token(
        tenant_id,
        client_id,
        "app/server.p12",
        ""
    )

    conn_info = psrp.WSManInfo(
        server="https://outlook.office365.com/PowerShell-LiveId?BasicAuthToOAuthConversion=true",
        auth="basic",
        username=f"OAuthUser@{tenant_id}",
        password=msal_token,
        configuration_name="Microsoft.Exchange"
    )

    with psrp.SyncRunspacePool(conn_info) as rp:
        ps = psrp.SyncPowerShell(rp)
        ps.add_command("Get-Mailbox")
        print(ps.invoke())
        print("done")

main()
jborean93 commented 11 months ago

I believe Exchange Online has stopped supporting PSRemoting endpoints with OAuth as per https://techcommunity.microsoft.com/t5/exchange-team-blog/deprecation-of-remote-powershell-rps-protocol-in-security-and/ba-p/3815432

The timeline of disablements in this blog post applies only to tenants in our World-Wide (WW) cloud. Final RPS disablement for customers in other Microsoft cloud environments as well as WW cloud is scheduled for October 2023.

They have their own REST API now with the new cmdlets wwhich means you can't use the PSRP library for online exchange management anymore.