mip-wrapper 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 mip-wrapper contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,103 @@
1
+ Metadata-Version: 2.4
2
+ Name: mip-wrapper
3
+ Version: 0.1.0
4
+ Summary: Unofficial Python wrapper for Microsoft Information Protection file decryption
5
+ License-Expression: MIT
6
+ Project-URL: Repository, https://github.com/firaun2020/mip-wrapper
7
+ Project-URL: Documentation, https://github.com/firaun2020/mip-wrapper/tree/main/docs
8
+ Project-URL: Issues, https://github.com/firaun2020/mip-wrapper/issues
9
+ Keywords: mip,information-protection,azure-rights-management,decryption
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Natural Language :: English
13
+ Classifier: Operating System :: Microsoft :: Windows
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: Software Development :: Libraries
18
+ Requires-Python: >=3.11
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: packaging>=21.0
22
+ Dynamic: license-file
23
+
24
+ # mip-wrapper
25
+
26
+ Unofficial Python wrapper for inspecting and decrypting files protected by Microsoft Purview Information Protection. The Python package starts a local JSON subprocess bridge to a separately built .NET helper, which calls the Microsoft Information Protection File SDK.
27
+
28
+ ## Scope
29
+
30
+ - Windows x64 only
31
+ - Python 3.11 or newer
32
+ - `delegated_reader` is the only supported authorization mode
33
+ - Unattended client-secret authentication via MSAL
34
+ - Local file inspection and temporary decryption
35
+ - No DRM bypass: the delegated user must have the required document usage right
36
+
37
+ The package does not bundle the .NET helper or Microsoft MIP SDK binaries. Build and install the helper separately, and ensure its Microsoft SDK licensing and redistribution terms are satisfied for your environment.
38
+
39
+ ## Installation
40
+
41
+ ```powershell
42
+ python -m pip install mip-wrapper
43
+ dotnet publish native/MipWrapper.Helper/MipWrapper.Helper.csproj -c Release -o helper-bin
44
+ $env:MIP_WRAPPER_HELPER_PATH = (Resolve-Path helper-bin/MipWrapper.Helper.exe)
45
+ ```
46
+
47
+ The helper requires the .NET runtime targeted by the project and the native Microsoft MIP SDK runtime available to it. `MIP_WRAPPER_HELPER_PATH` may point to the published helper executable. The source repository's `helper-bin/` directory is a local build output and is not part of the Python distribution.
48
+
49
+ ## Usage
50
+
51
+ ```python
52
+ import os
53
+ from mip_wrapper import MipClient
54
+ from mip_wrapper.auth import ClientSecretAuth
55
+
56
+ auth = ClientSecretAuth(
57
+ tenant_id=os.environ["MIP_TENANT_ID"],
58
+ client_id=os.environ["MIP_CLIENT_ID"],
59
+ secret_provider=lambda: os.environ["MIP_CLIENT_SECRET"],
60
+ )
61
+
62
+ client = MipClient(
63
+ auth=auth,
64
+ authorization_mode="delegated_reader",
65
+ delegated_user="reader@contoso.com",
66
+ )
67
+
68
+ info = client.inspect("protected.xlsx")
69
+ print(info.is_protected, info.usage_rights)
70
+
71
+ with client.decrypted_file("protected.xlsx") as file:
72
+ process(file.path)
73
+ ```
74
+
75
+ The context manager creates a temporary directory, returns the committed plaintext path, and removes the directory on normal exit and exceptions. A cleanup failure raises `CleanupError`. The original protected file is not used as the output path.
76
+
77
+ ## Entra and document authorization
78
+
79
+ The app registration must be configured for the Microsoft Information Protection resource and granted the application permission required by the MIP SDK's delegated-reader flow, documented by Microsoft as `Content.DelegatedReader`, with tenant-admin consent. `delegated_reader` is package configuration terminology, not an Entra permission. The delegated user must independently have `Export` or `Owner` usage rights on the protected file; neither the package mode nor the app permission grants document access by itself.
80
+
81
+ The helper acquires app-only tokens with `AcquireTokenForClient`, using the tenant supplied by the request and the MIP SDK challenge resource as `{resource}/.default`. It does not perform an interactive delegated-user sign-in.
82
+
83
+ ## Security and operational limits
84
+
85
+ - Do not put client secrets in source code, command-line arguments, logs, or protocol diagnostics. Supply them through a secret provider such as an environment-backed secret store.
86
+ - Protected file contents are written temporarily in plaintext while inside the context manager. Restrict host access and process only authorized content.
87
+ - The helper protocol is local and line-delimited JSON; it is not an authenticated network service.
88
+ - The package does not download, install, or update the helper or Microsoft SDK runtime.
89
+ - Real tenant credentials and protected files are required for integration testing. Unit tests use fake helper processes and do not prove tenant authorization or MIP service connectivity.
90
+
91
+ ## Development
92
+
93
+ ```powershell
94
+ python -m pip install -e .
95
+ python -m pytest
96
+ dotnet build native/MipWrapper.Helper/MipWrapper.Helper.csproj -c Release
97
+ ```
98
+
99
+ See [`BUILD_LOCALLY.md`](BUILD_LOCALLY.md) for the local helper workflow. See [`docs/distribution-and-licensing.md`](docs/distribution-and-licensing.md) for the unresolved Microsoft SDK distribution and licensing questions. This project is not affiliated with or endorsed by Microsoft.
100
+
101
+ ## License
102
+
103
+ The wrapper source is MIT licensed. The Microsoft Information Protection SDK and its native runtime are separately licensed by Microsoft; this repository does not grant rights to redistribute them.
@@ -0,0 +1,80 @@
1
+ # mip-wrapper
2
+
3
+ Unofficial Python wrapper for inspecting and decrypting files protected by Microsoft Purview Information Protection. The Python package starts a local JSON subprocess bridge to a separately built .NET helper, which calls the Microsoft Information Protection File SDK.
4
+
5
+ ## Scope
6
+
7
+ - Windows x64 only
8
+ - Python 3.11 or newer
9
+ - `delegated_reader` is the only supported authorization mode
10
+ - Unattended client-secret authentication via MSAL
11
+ - Local file inspection and temporary decryption
12
+ - No DRM bypass: the delegated user must have the required document usage right
13
+
14
+ The package does not bundle the .NET helper or Microsoft MIP SDK binaries. Build and install the helper separately, and ensure its Microsoft SDK licensing and redistribution terms are satisfied for your environment.
15
+
16
+ ## Installation
17
+
18
+ ```powershell
19
+ python -m pip install mip-wrapper
20
+ dotnet publish native/MipWrapper.Helper/MipWrapper.Helper.csproj -c Release -o helper-bin
21
+ $env:MIP_WRAPPER_HELPER_PATH = (Resolve-Path helper-bin/MipWrapper.Helper.exe)
22
+ ```
23
+
24
+ The helper requires the .NET runtime targeted by the project and the native Microsoft MIP SDK runtime available to it. `MIP_WRAPPER_HELPER_PATH` may point to the published helper executable. The source repository's `helper-bin/` directory is a local build output and is not part of the Python distribution.
25
+
26
+ ## Usage
27
+
28
+ ```python
29
+ import os
30
+ from mip_wrapper import MipClient
31
+ from mip_wrapper.auth import ClientSecretAuth
32
+
33
+ auth = ClientSecretAuth(
34
+ tenant_id=os.environ["MIP_TENANT_ID"],
35
+ client_id=os.environ["MIP_CLIENT_ID"],
36
+ secret_provider=lambda: os.environ["MIP_CLIENT_SECRET"],
37
+ )
38
+
39
+ client = MipClient(
40
+ auth=auth,
41
+ authorization_mode="delegated_reader",
42
+ delegated_user="reader@contoso.com",
43
+ )
44
+
45
+ info = client.inspect("protected.xlsx")
46
+ print(info.is_protected, info.usage_rights)
47
+
48
+ with client.decrypted_file("protected.xlsx") as file:
49
+ process(file.path)
50
+ ```
51
+
52
+ The context manager creates a temporary directory, returns the committed plaintext path, and removes the directory on normal exit and exceptions. A cleanup failure raises `CleanupError`. The original protected file is not used as the output path.
53
+
54
+ ## Entra and document authorization
55
+
56
+ The app registration must be configured for the Microsoft Information Protection resource and granted the application permission required by the MIP SDK's delegated-reader flow, documented by Microsoft as `Content.DelegatedReader`, with tenant-admin consent. `delegated_reader` is package configuration terminology, not an Entra permission. The delegated user must independently have `Export` or `Owner` usage rights on the protected file; neither the package mode nor the app permission grants document access by itself.
57
+
58
+ The helper acquires app-only tokens with `AcquireTokenForClient`, using the tenant supplied by the request and the MIP SDK challenge resource as `{resource}/.default`. It does not perform an interactive delegated-user sign-in.
59
+
60
+ ## Security and operational limits
61
+
62
+ - Do not put client secrets in source code, command-line arguments, logs, or protocol diagnostics. Supply them through a secret provider such as an environment-backed secret store.
63
+ - Protected file contents are written temporarily in plaintext while inside the context manager. Restrict host access and process only authorized content.
64
+ - The helper protocol is local and line-delimited JSON; it is not an authenticated network service.
65
+ - The package does not download, install, or update the helper or Microsoft SDK runtime.
66
+ - Real tenant credentials and protected files are required for integration testing. Unit tests use fake helper processes and do not prove tenant authorization or MIP service connectivity.
67
+
68
+ ## Development
69
+
70
+ ```powershell
71
+ python -m pip install -e .
72
+ python -m pytest
73
+ dotnet build native/MipWrapper.Helper/MipWrapper.Helper.csproj -c Release
74
+ ```
75
+
76
+ See [`BUILD_LOCALLY.md`](BUILD_LOCALLY.md) for the local helper workflow. See [`docs/distribution-and-licensing.md`](docs/distribution-and-licensing.md) for the unresolved Microsoft SDK distribution and licensing questions. This project is not affiliated with or endorsed by Microsoft.
77
+
78
+ ## License
79
+
80
+ The wrapper source is MIT licensed. The Microsoft Information Protection SDK and its native runtime are separately licensed by Microsoft; this repository does not grant rights to redistribute them.
@@ -0,0 +1,51 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "mip-wrapper"
7
+ version = "0.1.0"
8
+ description = "Unofficial Python wrapper for Microsoft Information Protection file decryption"
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = "MIT"
12
+ keywords = ["mip", "information-protection", "azure-rights-management", "decryption"]
13
+ classifiers = [
14
+ "Development Status :: 3 - Alpha",
15
+ "Intended Audience :: Developers",
16
+ "Natural Language :: English",
17
+ "Operating System :: Microsoft :: Windows",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.11",
20
+ "Programming Language :: Python :: 3.12",
21
+ "Topic :: Software Development :: Libraries",
22
+ ]
23
+
24
+ dependencies = [
25
+ "packaging>=21.0",
26
+ ]
27
+
28
+ [project.urls]
29
+ Repository = "https://github.com/firaun2020/mip-wrapper"
30
+ Documentation = "https://github.com/firaun2020/mip-wrapper/tree/main/docs"
31
+ Issues = "https://github.com/firaun2020/mip-wrapper/issues"
32
+
33
+ [tool.setuptools]
34
+ packages = ["mip_wrapper", "mip_wrapper.bridge"]
35
+ package-dir = {"" = "src"}
36
+
37
+ [tool.setuptools.package-data]
38
+ mip_wrapper = ["py.typed"]
39
+
40
+ [tool.mypy]
41
+ python_version = "3.11"
42
+ warn_return_any = true
43
+ warn_unused_configs = true
44
+ disallow_untyped_defs = true
45
+ disallow_incomplete_defs = true
46
+
47
+ [tool.pytest.ini_options]
48
+ testpaths = ["tests"]
49
+ python_files = ["test_*.py"]
50
+ python_classes = ["Test*"]
51
+ python_functions = ["test_*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,47 @@
1
+ """MIP Wrapper: Python interface for Microsoft Information Protection decryption."""
2
+
3
+ from mip_wrapper.client import MipClient
4
+ from mip_wrapper.exceptions import (
5
+ AuthenticationError,
6
+ AuthorizationError,
7
+ CleanupError,
8
+ DecryptionError,
9
+ DestinationError,
10
+ InvalidConfigurationError,
11
+ MipError,
12
+ MissingRuntimeError,
13
+ NativeRuntimeError,
14
+ PermissionDeniedError,
15
+ ProtocolError,
16
+ UnsupportedFileTypeError,
17
+ UnsupportedProtectionError,
18
+ )
19
+ from mip_wrapper.auth import AuthBase, CertificateAuth, ClientSecretAuth
20
+ from mip_wrapper.artifacts import DecryptedFile, FileInfo
21
+ from mip_wrapper.version import PACKAGE_VERSION, PROTOCOL_VERSION
22
+
23
+ __version__ = PACKAGE_VERSION
24
+
25
+ __all__ = [
26
+ "MipClient",
27
+ "MipError",
28
+ "AuthenticationError",
29
+ "AuthorizationError",
30
+ "PermissionDeniedError",
31
+ "UnsupportedProtectionError",
32
+ "UnsupportedFileTypeError",
33
+ "InvalidConfigurationError",
34
+ "NativeRuntimeError",
35
+ "ProtocolError",
36
+ "DecryptionError",
37
+ "CleanupError",
38
+ "DestinationError",
39
+ "MissingRuntimeError",
40
+ "AuthBase",
41
+ "CertificateAuth",
42
+ "ClientSecretAuth",
43
+ "DecryptedFile",
44
+ "FileInfo",
45
+ "PACKAGE_VERSION",
46
+ "PROTOCOL_VERSION",
47
+ ]
@@ -0,0 +1,82 @@
1
+ """Decrypted file artifacts."""
2
+
3
+ from datetime import datetime, timezone
4
+ from pathlib import Path
5
+
6
+
7
+ class DecryptedFile:
8
+ """Artifact representing a decrypted temporary file."""
9
+
10
+ def __init__(
11
+ self,
12
+ path: Path,
13
+ filename: str,
14
+ file_format: str,
15
+ size_bytes: int | None = None,
16
+ audit_metadata: dict[str, str] | None = None,
17
+ ) -> None:
18
+ """
19
+ Initialize decrypted file artifact.
20
+
21
+ Args:
22
+ path: Path to the decrypted file
23
+ filename: Original filename
24
+ file_format: File extension (.xlsx, .pdf, etc.)
25
+ size_bytes: Size of decrypted file (if known)
26
+ audit_metadata: Safe audit metadata
27
+ """
28
+ self.path = path
29
+ self.filename = filename
30
+ self.file_format = file_format
31
+ self.size_bytes = size_bytes
32
+ self.audit_metadata = audit_metadata or {}
33
+ self.decryption_timestamp = datetime.now(timezone.utc)
34
+
35
+ def __repr__(self) -> str:
36
+ return f"DecryptedFile(path={self.path}, filename={self.filename})"
37
+
38
+
39
+ class FileInfo:
40
+ """Metadata about a protected file."""
41
+
42
+ def __init__(
43
+ self,
44
+ is_protected: bool,
45
+ label_id: str | None,
46
+ tenant_id: str | None,
47
+ file_format: str,
48
+ protection_type: str,
49
+ usage_rights: list[str],
50
+ can_decrypt: bool,
51
+ sdk_version: str,
52
+ helper_version: str,
53
+ ) -> None:
54
+ """
55
+ Initialize file info.
56
+
57
+ Args:
58
+ is_protected: Whether file is MIP-protected
59
+ label_id: Sensitivity label ID (if available)
60
+ tenant_id: Tenant ID where file is protected
61
+ file_format: File extension
62
+ protection_type: Type of protection (azure_rms, etc.)
63
+ usage_rights: Available rights for current user
64
+ can_decrypt: Whether current config can decrypt
65
+ sdk_version: MIP SDK version
66
+ helper_version: Helper version
67
+ """
68
+ self.is_protected = is_protected
69
+ self.label_id = label_id
70
+ self.tenant_id = tenant_id
71
+ self.file_format = file_format
72
+ self.protection_type = protection_type
73
+ self.usage_rights = usage_rights
74
+ self.can_decrypt = can_decrypt
75
+ self.sdk_version = sdk_version
76
+ self.helper_version = helper_version
77
+
78
+ def __repr__(self) -> str:
79
+ return (
80
+ f"FileInfo(protected={self.is_protected}, "
81
+ f"label={self.label_id}, rights={self.usage_rights})"
82
+ )
@@ -0,0 +1,101 @@
1
+ """Authentication configuration."""
2
+
3
+ from abc import ABC, abstractmethod
4
+ from pathlib import Path
5
+
6
+ from mip_wrapper.exceptions import InvalidConfigurationError
7
+
8
+
9
+ class AuthBase(ABC):
10
+ """Base class for authentication methods."""
11
+
12
+ tenant_id: str
13
+ client_id: str
14
+
15
+ @abstractmethod
16
+ def validate(self) -> None:
17
+ """Validate configuration is complete."""
18
+ pass
19
+
20
+
21
+ class CertificateAuth(AuthBase):
22
+ """Certificate-based authentication (recommended)."""
23
+
24
+ def __init__(
25
+ self,
26
+ tenant_id: str,
27
+ client_id: str,
28
+ certificate_path: str,
29
+ certificate_password_path: str | None = None,
30
+ ) -> None:
31
+ """
32
+ Configure certificate-based authentication.
33
+
34
+ Args:
35
+ tenant_id: Azure AD tenant ID (UUID or domain)
36
+ client_id: App registration client ID (UUID)
37
+ certificate_path: Path to .pfx certificate file
38
+ certificate_password_path: Path to file containing certificate password
39
+ (optional if cert has no password)
40
+ """
41
+ self.tenant_id = tenant_id
42
+ self.client_id = client_id
43
+ self.certificate_path = certificate_path
44
+ self.certificate_password_path = certificate_password_path
45
+
46
+ def validate(self) -> None:
47
+ """Validate certificate paths exist."""
48
+ if not self.tenant_id or not self.client_id:
49
+ raise InvalidConfigurationError(
50
+ "tenant_id and client_id are required",
51
+ error_code="MissingAuth",
52
+ )
53
+
54
+ cert_path = Path(self.certificate_path)
55
+ if not cert_path.exists():
56
+ raise InvalidConfigurationError(
57
+ f"Certificate file not found: {self.certificate_path}",
58
+ error_code="CertificateNotFound",
59
+ )
60
+
61
+ if self.certificate_password_path:
62
+ pwd_path = Path(self.certificate_password_path)
63
+ if not pwd_path.exists():
64
+ raise InvalidConfigurationError(
65
+ f"Certificate password file not found: {self.certificate_password_path}",
66
+ error_code="PasswordFileNotFound",
67
+ )
68
+
69
+
70
+ class ClientSecretAuth(AuthBase):
71
+ """Client secret authentication (less secure, for development)."""
72
+
73
+ def __init__(
74
+ self,
75
+ tenant_id: str,
76
+ client_id: str,
77
+ secret_provider: callable,
78
+ ) -> None:
79
+ """
80
+ Configure client secret authentication.
81
+
82
+ Args:
83
+ tenant_id: Azure AD tenant ID
84
+ client_id: App registration client ID
85
+ secret_provider: Callable that returns the client secret
86
+
87
+ Note:
88
+ Client secrets should be stored securely (Key Vault, etc.)
89
+ This method is less secure than certificate authentication.
90
+ """
91
+ self.tenant_id = tenant_id
92
+ self.client_id = client_id
93
+ self.secret_provider = secret_provider
94
+
95
+ def validate(self) -> None:
96
+ """Validate configuration."""
97
+ if not self.tenant_id or not self.client_id:
98
+ raise InvalidConfigurationError(
99
+ "tenant_id and client_id are required",
100
+ error_code="MissingAuth",
101
+ )
@@ -0,0 +1,17 @@
1
+ """Bridge between Python and .NET helper."""
2
+
3
+ from mip_wrapper.bridge.client import HelperClient
4
+ from mip_wrapper.bridge.protocol import (
5
+ DecryptResult,
6
+ InspectResult,
7
+ ProtocolRequest,
8
+ ProtocolResponse,
9
+ )
10
+
11
+ __all__ = [
12
+ "HelperClient",
13
+ "ProtocolRequest",
14
+ "ProtocolResponse",
15
+ "InspectResult",
16
+ "DecryptResult",
17
+ ]