Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 18 additions & 18 deletions application/single_app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@
EXECUTOR_TYPE = 'thread'
EXECUTOR_MAX_WORKERS = 30
SESSION_TYPE = 'filesystem'
VERSION = "0.239.002"
VERSION = "0.239.004"

SECRET_KEY = os.getenv('SECRET_KEY', 'dev-secret-key-change-in-production')

Expand Down Expand Up @@ -176,12 +176,14 @@ def get_allowed_extensions(enable_video=False, enable_audio=False):

# Add Support for Custom Azure Environments
CUSTOM_GRAPH_URL_VALUE = os.getenv("CUSTOM_GRAPH_URL_VALUE", "")
CUSTOM_GRAPH_AUTHORITY_URL_VALUE = os.getenv("CUSTOM_GRAPH_AUTHORITY_URL_VALUE", "")
CUSTOM_IDENTITY_URL_VALUE = os.getenv("CUSTOM_IDENTITY_URL_VALUE", "")
CUSTOM_RESOURCE_MANAGER_URL_VALUE = os.getenv("CUSTOM_RESOURCE_MANAGER_URL_VALUE", "")
CUSTOM_BLOB_STORAGE_URL_VALUE = os.getenv("CUSTOM_BLOB_STORAGE_URL_VALUE", "")
CUSTOM_COGNITIVE_SERVICES_URL_VALUE = os.getenv("CUSTOM_COGNITIVE_SERVICES_URL_VALUE", "")
CUSTOM_SEARCH_RESOURCE_MANAGER_URL_VALUE = os.getenv("CUSTOM_SEARCH_RESOURCE_MANAGER_URL_VALUE", "")
CUSTOM_REDIS_CACHE_INFRASTRUCTURE_URL_VALUE = os.getenv("CUSTOM_REDIS_CACHE_INFRASTRUCTURE_URL_VALUE", "")
CUSTOM_OIDC_METADATA_URL_VALUE = os.getenv("CUSTOM_OIDC_METADATA_URL_VALUE", "")


# Azure AD Configuration
Expand All @@ -193,41 +195,39 @@ def get_allowed_extensions(enable_video=False, enable_audio=False):
MICROSOFT_PROVIDER_AUTHENTICATION_SECRET = os.getenv("MICROSOFT_PROVIDER_AUTHENTICATION_SECRET")
LOGIN_REDIRECT_URL = os.getenv("LOGIN_REDIRECT_URL")
HOME_REDIRECT_URL = os.getenv("HOME_REDIRECT_URL") # Front Door URL for home page

OIDC_METADATA_URL = f"https://login.microsoftonline.com/{TENANT_ID}/v2.0/.well-known/openid-configuration"
AZURE_ENVIRONMENT = os.getenv("AZURE_ENVIRONMENT", "public") # public, usgovernment, custom

if AZURE_ENVIRONMENT == "custom":
WORD_CHUNK_SIZE = 400

if AZURE_ENVIRONMENT == "custom" or CUSTOM_IDENTITY_URL_VALUE or CUSTOM_GRAPH_AUTHORITY_URL_VALUE:
AUTHORITY = f"{CUSTOM_IDENTITY_URL_VALUE}/{TENANT_ID}"
authority = CUSTOM_GRAPH_AUTHORITY_URL_VALUE or CUSTOM_IDENTITY_URL_VALUE or AUTHORITY.rstrip(f'/{TENANT_ID}')
elif AZURE_ENVIRONMENT == "usgovernment":
AUTHORITY = f"https://login.microsoftonline.us/{TENANT_ID}"
authority = AzureAuthorityHosts.AZURE_GOVERNMENT
else:
AUTHORITY = f"https://login.microsoftonline.com/{TENANT_ID}"
authority = AzureAuthorityHosts.AZURE_PUBLIC_CLOUD

WORD_CHUNK_SIZE = 400

if AZURE_ENVIRONMENT == "usgovernment":
if AZURE_ENVIRONMENT == "custom":
OIDC_METADATA_URL = CUSTOM_OIDC_METADATA_URL_VALUE or f"https://login.microsoftonline.com/{TENANT_ID}/v2.0/.well-known/openid-configuration"
resource_manager = CUSTOM_RESOURCE_MANAGER_URL_VALUE
video_indexer_endpoint = os.getenv("CUSTOM_VIDEO_INDEXER_ENDPOINT", "https://api.videoindexer.ai")
credential_scopes=[resource_manager + "/.default"]
cognitive_services_scope = CUSTOM_COGNITIVE_SERVICES_URL_VALUE
search_resource_manager = CUSTOM_SEARCH_RESOURCE_MANAGER_URL_VALUE
KEY_VAULT_DOMAIN = os.getenv("KEY_VAULT_DOMAIN", ".vault.azure.net")
elif AZURE_ENVIRONMENT == "usgovernment":
OIDC_METADATA_URL = f"https://login.microsoftonline.us/{TENANT_ID}/v2.0/.well-known/openid-configuration"
resource_manager = "https://management.usgovcloudapi.net"
authority = AzureAuthorityHosts.AZURE_GOVERNMENT
credential_scopes=[resource_manager + "/.default"]
cognitive_services_scope = "https://cognitiveservices.azure.us/.default"
video_indexer_endpoint = "https://api.videoindexer.ai.azure.us"
search_resource_manager = "https://search.azure.us"
KEY_VAULT_DOMAIN = ".vault.usgovcloudapi.net"

elif AZURE_ENVIRONMENT == "custom":
resource_manager = CUSTOM_RESOURCE_MANAGER_URL_VALUE
authority = CUSTOM_IDENTITY_URL_VALUE
video_indexer_endpoint = os.getenv("CUSTOM_VIDEO_INDEXER_ENDPOINT", "https://api.videoindexer.ai")
credential_scopes=[resource_manager + "/.default"]
cognitive_services_scope = CUSTOM_COGNITIVE_SERVICES_URL_VALUE
search_resource_manager = CUSTOM_SEARCH_RESOURCE_MANAGER_URL_VALUE
KEY_VAULT_DOMAIN = os.getenv("KEY_VAULT_DOMAIN", ".vault.azure.net")
else:
OIDC_METADATA_URL = f"https://login.microsoftonline.com/{TENANT_ID}/v2.0/.well-known/openid-configuration"
resource_manager = "https://management.azure.com"
authority = AzureAuthorityHosts.AZURE_PUBLIC_CLOUD
credential_scopes=[resource_manager + "/.default"]
cognitive_services_scope = "https://cognitiveservices.azure.com/.default"
video_indexer_endpoint = "https://api.videoindexer.ai"
Expand Down
7 changes: 6 additions & 1 deletion application/single_app/example.env
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,9 @@ TENANT_ID="<your-azure-ad-tenant-id>"
SECRET_KEY="Generate-A-Strong-Random-Secret-Key-Here!"
# AZURE_ENVIRONMENT: Set based on your cloud environment
# Options: "public", "usgovernment", "custom"
AZURE_ENVIRONMENT="public"
AZURE_ENVIRONMENT="public"

# Optional Graph overrides (for cross-cloud identity/Graph scenarios)
# Example values:
# CUSTOM_GRAPH_URL_VALUE="https://graph.microsoft.com"
# CUSTOM_GRAPH_AUTHORITY_URL_VALUE="https://login.microsoftonline.com"
114 changes: 103 additions & 11 deletions application/single_app/functions_authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,12 @@ def _save_cache(cache):
# Decide how to handle this, maybe clear cache or log extensively
# session.pop("token_cache", None) # Option: Clear on serialization failure

def _build_msal_app(cache=None):
def _build_msal_app(cache=None, authority_override=None):
"""Builds the MSAL ConfidentialClientApplication, optionally initializing with a cache."""
authority = authority_override or AUTHORITY
return ConfidentialClientApplication(
CLIENT_ID,
authority=AUTHORITY,
authority=authority,
client_credential=CLIENT_SECRET,
token_cache=cache # Pass the cache instance here
)
Expand Down Expand Up @@ -88,7 +89,7 @@ def get_valid_access_token(scopes=None):

required_scopes = scopes or SCOPE # Use default SCOPE if none provided

msal_app = _build_msal_app(cache=_load_cache())
msal_app = _build_msal_app(cache=_load_cache(), authority_override=get_graph_authority())
user_info = session.get("user", {})
# MSAL uses home_account_id which combines oid and tid
# Construct it carefully based on your id_token_claims structure
Expand Down Expand Up @@ -160,7 +161,7 @@ def get_valid_access_token_for_plugins(scopes=None):

required_scopes = scopes or SCOPE # Use default SCOPE if none provided

msal_app = _build_msal_app(cache=_load_cache())
msal_app = _build_msal_app(cache=_load_cache(), authority_override=get_graph_authority())
user_info = session.get("user", {})
# MSAL uses home_account_id which combines oid and tid
# Construct it carefully based on your id_token_claims structure
Expand Down Expand Up @@ -844,6 +845,103 @@ def get_current_user_info():
"displayName": user.get("name")
}


def _normalize_authority(authority_base, tenant_id):
"""Normalize an authority URL and append tenant when appropriate."""
base = (authority_base or "").strip().rstrip("/")
tenant = (tenant_id or "").strip()

if not base or not tenant:
return base

lowered = base.lower()
tenant_lower = tenant.lower()

if lowered.endswith(f"/{tenant_lower}"):
return base

if lowered.endswith("/common") or lowered.endswith("/organizations") or lowered.endswith("/consumers"):
return base

return f"{base}/{tenant}"


def get_graph_authority():
"""
Resolve authority for Graph token acquisition, independent of general Azure environment defaults.

Precedence:
1. CUSTOM_GRAPH_AUTHORITY_URL_VALUE if provided
2. Custom cloud identity authority for AZURE_ENVIRONMENT=custom
3. Gov/Public cloud authority based on AZURE_ENVIRONMENT
"""
custom_graph_authority = (CUSTOM_GRAPH_AUTHORITY_URL_VALUE or "").strip()
if custom_graph_authority:
return _normalize_authority(custom_graph_authority, TENANT_ID)

if AZURE_ENVIRONMENT == "custom":
return _normalize_authority(CUSTOM_IDENTITY_URL_VALUE, TENANT_ID)

if AZURE_ENVIRONMENT == "usgovernment":
return f"https://login.microsoftonline.us/{TENANT_ID}"

return f"https://login.microsoftonline.com/{TENANT_ID}"


def get_graph_base_url():
"""
Resolve the Microsoft Graph base URL for this deployment.

Precedence:
1. CUSTOM_GRAPH_URL_VALUE if provided (works in any AZURE_ENVIRONMENT mode)
2. Azure Gov Graph for usgovernment
3. Public Graph by default

Returns:
str: Normalized Graph base URL ending with /v1.0
"""
custom_graph_url = (CUSTOM_GRAPH_URL_VALUE or "").strip().rstrip("/")
if custom_graph_url:
normalized = custom_graph_url
lowered = normalized.lower()

# Allow legacy values such as https://.../v1.0/users
if lowered.endswith("/users"):
normalized = normalized[:-6].rstrip("/")
lowered = normalized.lower()

if "/v1.0" not in lowered:
normalized = f"{normalized}/v1.0"

return normalized

if AZURE_ENVIRONMENT == "usgovernment":
return "https://graph.microsoft.us/v1.0"

return "https://graph.microsoft.com/v1.0"


def get_graph_endpoint(path=""):
"""
Build a full Graph endpoint from a relative path.

Args:
path (str): Relative Graph path (for example: "/users" or "users/{id}")

Returns:
str: Fully qualified Microsoft Graph URL
"""
base_url = get_graph_base_url().rstrip("/")
path = (path or "").strip()

if not path:
return base_url

if not path.startswith("/"):
path = f"/{path}"

return f"{base_url}{path}"

def get_user_profile_image():
"""
Fetches the user's profile image from Microsoft Graph and returns it as base64.
Expand All @@ -854,13 +952,7 @@ def get_user_profile_image():
debug_print("get_user_profile_image: Could not acquire access token")
return None

# Determine the correct Graph endpoint based on Azure environment
if AZURE_ENVIRONMENT == "usgovernment":
profile_image_endpoint = "https://graph.microsoft.us/v1.0/me/photo/$value"
elif AZURE_ENVIRONMENT == "custom":
profile_image_endpoint = f"{CUSTOM_GRAPH_URL_VALUE}/me/photo/$value"
else:
profile_image_endpoint = "https://graph.microsoft.com/v1.0/me/photo/$value"
profile_image_endpoint = get_graph_endpoint("/me/photo/$value")

headers = {
"Authorization": f"Bearer {token}",
Expand Down
2 changes: 1 addition & 1 deletion application/single_app/route_backend_documents.py
Original file line number Diff line number Diff line change
Expand Up @@ -1378,7 +1378,7 @@ def api_get_shared_users(document_id):
approval_status = entry.get('approval_status', 'unknown')
try:
# Get user details from Microsoft Graph
graph_url = f"https://graph.microsoft.com/v1.0/users/{oid}"
graph_url = get_graph_endpoint(f"/users/{oid}")
response = requests.get(graph_url, headers=headers)

if response.status_code == 200:
Expand Down
7 changes: 1 addition & 6 deletions application/single_app/route_backend_public_workspaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,7 @@ def get_user_details_from_graph(user_id):
if not token:
return {"displayName": "", "email": ""}

if AZURE_ENVIRONMENT == "usgovernment":
user_endpoint = f"https://graph.microsoft.us/v1.0/users/{user_id}"
elif AZURE_ENVIRONMENT == "custom":
user_endpoint = f"{CUSTOM_GRAPH_URL_VALUE}/{user_id}"
else:
user_endpoint = f"https://graph.microsoft.com/v1.0/users/{user_id}"
user_endpoint = get_graph_endpoint(f"/users/{user_id}")

headers = {
"Authorization": f"Bearer {token}",
Expand Down
7 changes: 1 addition & 6 deletions application/single_app/route_backend_users.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,7 @@ def api_user_search():
if not token:
return jsonify({"error": "Could not acquire access token"}), 401

if AZURE_ENVIRONMENT == "usgovernment":
user_endpoint = "https://graph.microsoft.us/v1.0/users"
elif AZURE_ENVIRONMENT == "custom":
user_endpoint = CUSTOM_GRAPH_URL_VALUE
else:
user_endpoint = "https://graph.microsoft.com/v1.0/users"
user_endpoint = get_graph_endpoint("/users")

headers = {
"Authorization": f"Bearer {token}",
Expand Down
Loading