diff --git a/dfp_external_storage/dfp_external_storage/doctype/dfp_external_storage/dfp_external_storage.js b/dfp_external_storage/dfp_external_storage/doctype/dfp_external_storage/dfp_external_storage.js
index ec59391..5d91c2a 100644
--- a/dfp_external_storage/dfp_external_storage/doctype/dfp_external_storage/dfp_external_storage.js
+++ b/dfp_external_storage/dfp_external_storage/doctype/dfp_external_storage/dfp_external_storage.js
@@ -5,7 +5,20 @@ frappe.ui.form.on('DFP External Storage', {
frm.button_remote_files_list = null
},
+ auth_type: function(frm) {
+ // Toggle visibility of credential fields based on auth type
+ frm.toggle_reqd('access_key', frm.doc.auth_type === 'Key based')
+ frm.toggle_reqd('secret_key', frm.doc.auth_type === 'Key based')
+ frm.refresh_field('access_key')
+ frm.refresh_field('secret_key')
+ },
+
refresh: function(frm) {
+ // Set initial required state based on auth_type
+ if (frm.doc.auth_type) {
+ frm.toggle_reqd('access_key', frm.doc.auth_type === 'Key based')
+ frm.toggle_reqd('secret_key', frm.doc.auth_type === 'Key based')
+ }
if (frm.is_new() && !frm.doc.doctypes_ignored.length) {
frm.doc.doctypes_ignored.push({doctype_to_ignore: 'Data Import'})
frm.doc.doctypes_ignored.push({doctype_to_ignore: 'Prepared Report'})
diff --git a/dfp_external_storage/dfp_external_storage/doctype/dfp_external_storage/dfp_external_storage.json b/dfp_external_storage/dfp_external_storage/doctype/dfp_external_storage/dfp_external_storage.json
index c3b2625..99c95de 100644
--- a/dfp_external_storage/dfp_external_storage/doctype/dfp_external_storage/dfp_external_storage.json
+++ b/dfp_external_storage/dfp_external_storage/doctype/dfp_external_storage/dfp_external_storage.json
@@ -16,6 +16,7 @@
"secure",
"bucket_name",
"region",
+ "auth_type",
"access_key",
"secret_key",
"folders",
@@ -44,6 +45,25 @@
"reqd": 1
},
{
+ "default": "auto",
+ "description": "Region name of buckets in S3 service. Default value: \"auto\".",
+ "documentation_url": "https://min.io/docs/minio/linux/developers/python/API.html",
+ "fieldname": "region",
+ "fieldtype": "Data",
+ "label": "Region",
+ "reqd": 1
+ },
+ {
+ "default": "Key based",
+ "description": "Authentication method to use. 'Key based' requires Access Key and Secret Key. You can also optionally provide session token if you have short-lived credentials. 'Aws Iam' to use AWS IAM credentials (e.g., from AWS credentials file or IAM role).",
+ "fieldname": "auth_type",
+ "fieldtype": "Select",
+ "label": "Authentication Type",
+ "options": "Key based\nAws Iam",
+ "reqd": 1
+ },
+ {
+ "depends_on": "eval:doc.auth_type == 'Key based'",
"description": "Access key (aka user ID) of your account in S3 service. If empty, will use environment variables (AWS_ACCESS_KEY_ID or MINIO_ACCESS_KEY or MINIO_ROOT_USER).",
"documentation_url": "https://min.io/docs/minio/linux/developers/python/API.html",
"fieldname": "access_key",
@@ -52,6 +72,7 @@
"label": "Access Key"
},
{
+ "depends_on": "eval:doc.auth_type == 'Key based'",
"description": "Secret Key (aka password) of your account in S3 service. If empty, will use environment variables (AWS_SECRET_ACCESS_KEY or MINIO_SECRET_KEY or MINIO_ROOT_PASSWORD).",
"documentation_url": "https://min.io/docs/minio/linux/developers/python/API.html",
"fieldname": "secret_key",
@@ -59,15 +80,6 @@
"label": "Secret Key",
"no_copy": 1
},
- {
- "default": "auto",
- "description": "Region name of buckets in S3 service. Default value: \"auto\".",
- "documentation_url": "https://min.io/docs/minio/linux/developers/python/API.html",
- "fieldname": "region",
- "fieldtype": "Data",
- "label": "Region",
- "reqd": 1
- },
{
"description": "Be careful! Please set up your bucket as private in your provider and/or remove public access to objects. Some files you upload to Frappe will be for sure private!",
"documentation_url": "https://min.io/docs/minio/linux/developers/python/API.html",
@@ -84,7 +96,7 @@
{
"fieldname": "html_1",
"fieldtype": "HTML",
- "options": "
Used Minio library (Simple Storage Service or S3) client to perform bucket and object operations), so please refer to it class initialization for more info:
\n\n- endpoint: Hostname of a S3 service.
\n- access_key: Access key (aka user ID) of your account in S3 service.
\n- secret_key: Secret Key (aka password) of your account in S3 service.
\n- session_token: Session token of your account in S3 service.
\n- secure: Flag to indicate to use secure (TLS) connection to S3 service or not.
\n
"
+ "options": "Used Minio library (Simple Storage Service or S3) client to perform bucket and object operations), so please refer to it class initialization for more info:
\n\n- endpoint: Hostname of a S3 service.
\n- access_key: Access key (aka user ID) of your account in S3 service.
\n- secret_key: Secret Key (aka password) of your account in S3 service.
\n- secure: Flag to indicate to use secure (TLS) connection to S3 service or not.
\n
"
},
{
"description": "Only host and port. For example:\n- [id].r2.cloudflarestorage.com:443
- s3.amazonaws.com
- minio:9000
- ...
\nRead Minio documentation for more info.",
diff --git a/dfp_external_storage/dfp_external_storage/doctype/dfp_external_storage/dfp_external_storage.py b/dfp_external_storage/dfp_external_storage/doctype/dfp_external_storage/dfp_external_storage.py
index e19aede..bf1f069 100644
--- a/dfp_external_storage/dfp_external_storage/doctype/dfp_external_storage/dfp_external_storage.py
+++ b/dfp_external_storage/dfp_external_storage/doctype/dfp_external_storage/dfp_external_storage.py
@@ -15,7 +15,8 @@
from frappe.core.doctype.file.file import URL_PREFIXES
from frappe.model.document import Document
from frappe.utils.password import get_decrypted_password
-
+from minio.credentials.providers import IamAwsProvider
+from minio.credentials.providers import StaticProvider
DFP_EXTERNAL_STORAGE_PUBLIC_CACHE_PREFIX = "external_storage_public_file:"
@@ -25,9 +26,9 @@
DFP_EXTERNAL_STORAGE_CONNECTION_FIELDS = [
- "type", "endpoint", "secure", "bucket_name", "region", "access_key", "secret_key"]
+ "type", "endpoint", "secure", "bucket_name", "region", "auth_type", "access_key", "secret_key"]
DFP_EXTERNAL_STORAGE_CRITICAL_FIELDS = [
- "type", "endpoint", "secure", "bucket_name", "region", "access_key", "secret_key", "folders"]
+ "type", "endpoint", "secure", "bucket_name", "region", "auth_type", "access_key", "secret_key", "folders"]
class S3FileProxy:
@@ -124,51 +125,73 @@ def validate_bucket(self):
@cached_property
def client(self):
- # Allow access_key/secret_key to be optional: if not provided in this DocType,
- # fallback to environment variables.
- if self.endpoint and self.region:
+ if not self.endpoint or not self.region:
+ return None
+
+ # Handle IAM authentication
+ if getattr(self, 'auth_type', 'Key based') == 'Aws Iam':
try:
- # Resolve access key
- access_key = self.access_key or \
- os.getenv("AWS_ACCESS_KEY_ID") or \
- os.getenv("MINIO_ACCESS_KEY") or \
- os.getenv("MINIO_ROOT_USER")
-
- # Resolve secret key
- if self.is_new() and self.secret_key:
- key_secret = self.secret_key
- elif self.secret_key:
- key_secret = get_decrypted_password("DFP External Storage", self.name, "secret_key") if self.name else None
- else:
- key_secret = None
-
- secret_key = key_secret or \
- os.getenv("AWS_SECRET_ACCESS_KEY") or \
- os.getenv("MINIO_SECRET_KEY") or \
- os.getenv("MINIO_ROOT_PASSWORD")
-
- if access_key and secret_key:
+ credentials = IamAwsProvider(
+ region=self.region,
+ )
+ if credentials:
return MinioConnection(
endpoint=self.endpoint,
- access_key=access_key,
- secret_key=secret_key,
+ credentials=credentials,
region=self.region,
secure=self.secure,
)
- except Exception:
+ except:
pass
+
+ # Handle Key based authentication
+ # Allow access_key/secret_key to be optional: if not provided in this DocType,
+ # fallback to environment variables.
+ try:
+ # Resolve access key
+ access_key = self.access_key or \
+ os.getenv("AWS_ACCESS_KEY_ID") or \
+ os.getenv("MINIO_ACCESS_KEY") or \
+ os.getenv("MINIO_ROOT_USER")
+
+ # Resolve secret key
+ if self.is_new() and self.secret_key:
+ key_secret = self.secret_key
+ elif self.secret_key:
+ key_secret = get_decrypted_password("DFP External Storage", self.name, "secret_key") if self.name else None
+ else:
+ key_secret = None
+
+ secret_key = key_secret or \
+ os.getenv("AWS_SECRET_ACCESS_KEY") or \
+ os.getenv("MINIO_SECRET_KEY") or \
+ os.getenv("MINIO_ROOT_PASSWORD")
+
+ credentials = StaticProvider(
+ access_key=access_key,
+ secret_key=secret_key,
+ )
+ return MinioConnection(
+ endpoint=self.endpoint,
+ credentials=credentials,
+ region=self.region,
+ secure=self.secure,
+ )
+ except Exception:
+ pass
+
+ return None
def remote_files_list(self):
return self.client.list_objects(self.bucket_name, recursive=True)
class MinioConnection:
- def __init__(self, endpoint:str, access_key:str, secret_key:str, region:str, secure:bool):
+ def __init__(self, endpoint:str, region:str, credentials:StaticProvider | IamAwsProvider, secure:bool):
self.client = Minio(
endpoint=endpoint,
- access_key=access_key,
- secret_key=secret_key,
region=region,
+ credentials=credentials,
secure=secure,
)