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.
- mip_wrapper-0.1.0/LICENSE +21 -0
- mip_wrapper-0.1.0/PKG-INFO +103 -0
- mip_wrapper-0.1.0/README.md +80 -0
- mip_wrapper-0.1.0/pyproject.toml +51 -0
- mip_wrapper-0.1.0/setup.cfg +4 -0
- mip_wrapper-0.1.0/src/mip_wrapper/__init__.py +47 -0
- mip_wrapper-0.1.0/src/mip_wrapper/artifacts.py +82 -0
- mip_wrapper-0.1.0/src/mip_wrapper/auth.py +101 -0
- mip_wrapper-0.1.0/src/mip_wrapper/bridge/__init__.py +17 -0
- mip_wrapper-0.1.0/src/mip_wrapper/bridge/client.py +271 -0
- mip_wrapper-0.1.0/src/mip_wrapper/bridge/protocol.py +159 -0
- mip_wrapper-0.1.0/src/mip_wrapper/client.py +260 -0
- mip_wrapper-0.1.0/src/mip_wrapper/exceptions.py +90 -0
- mip_wrapper-0.1.0/src/mip_wrapper/version.py +55 -0
- mip_wrapper-0.1.0/src/mip_wrapper.egg-info/PKG-INFO +103 -0
- mip_wrapper-0.1.0/src/mip_wrapper.egg-info/SOURCES.txt +17 -0
- mip_wrapper-0.1.0/src/mip_wrapper.egg-info/dependency_links.txt +1 -0
- mip_wrapper-0.1.0/src/mip_wrapper.egg-info/requires.txt +1 -0
- mip_wrapper-0.1.0/src/mip_wrapper.egg-info/top_level.txt +1 -0
|
@@ -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,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
|
+
]
|