Skip to content
Open
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
39 changes: 29 additions & 10 deletions web/pgadmin/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -601,8 +601,10 @@ def validate_json_data(data, is_admin):
for server in data["Servers"]:
obj = data["Servers"][server]

is_shared = obj.get("Shared", None)

# Check if server is shared.Won't import if user is non-admin
if obj.get('Shared', None) and not is_admin:
if is_shared and not is_admin:
print("Won't import the server '%s' as it is shared " %
obj["Name"])
skip_servers.append(server)
Expand All @@ -627,14 +629,25 @@ def check_is_integer(value):
is_service_attrib_available = obj.get("Service", None) is not None

if not is_service_attrib_available:
for attrib in ("Port", "Username"):
errmsg = check_attrib(attrib)
errmsg = check_attrib("Port")
if errmsg:
return errmsg
errmsg = check_is_integer(obj["Port"])
if errmsg:
return errmsg

if is_shared:
# Shared servers may carry either the owner's username
# or a per-user override, so accept either attribute.
if "Username" not in obj and "SharedUsername" not in obj:
return gettext(
"'Username' or 'SharedUsername' attribute not "
"found for server '%s'" % server
)
else:
errmsg = check_attrib("Username")
if errmsg:
return errmsg
if attrib == 'Port':
errmsg = check_is_integer(obj[attrib])
if errmsg:
return errmsg

errmsg = check_attrib("MaintenanceDB")
if errmsg:
Expand Down Expand Up @@ -720,6 +733,12 @@ def load_database_servers(input_file, selected_servers,
groups_added = groups_added + 1
groups = ServerGroup.query.filter_by(user_id=user_id)

is_shared = obj.get("Shared", None)
username = obj.get("Username", None)
shared_username = obj.get("SharedUsername", None)
if is_shared and not username:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @KijongHan , In case where username is a empty string in json data, it will fall back to SharedUsername. If SharedUsername is also missing, the server imports silently with username = None. We can use username is None to only trigger the fallback when the key is genuinely absent.

I was able to reproduce this with following json

{
  "Servers": {
    "1": {
      "Group": "Servers",
      "Name": "Test",
      "Shared": true,
      "Username": "",
      "Host": "127.0.0.1",
      "Port": 5432,
      "MaintenanceDB": "postgres"
    }
  }
}

Copy link
Copy Markdown
Contributor Author

@KijongHan KijongHan Jun 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @hiteshjambhale thanks alot for checking this use case! I share your concern around an empty username string being considered a valid import, but I'm thinking that we should short-circuit the import process further up the validation chain (during JSON validation) here that way we handle the validation in the right place

From what I can see, an empty string username being considered valid has been there for some time, so I want to check with you @asheshv or @akshay-joshi and get your opinion on whether we should tighten the validation logic on the username property and not allow empty strings

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @hiteshjambhale what do you think about raising the username empty string validation logic as a separate issue?

username = shared_username

Comment on lines +736 to +741
Copy link
Copy Markdown
Contributor Author

@KijongHan KijongHan Jun 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the import currently prefers Username over SharedUsername when both are present

I think this Username override makes sense, so that SharedUsername is only the default value (this is how it is currently treated in the UI creation stage as well at the moment)

and it does not populate shared_username when only legacy Username is provided

I dont think we should be treating shared_username property value like a fallback. It should be "pure". This feels aligned to how username is used as a network login (so fallback seems reasonable) but shared_username is treated as the "default username for these shared servers" so it doesn't make sense to me to introduce a fallback here. Keen for your thoughts @asheshv

# Create the server
new_server = Server()
new_server.name = obj["Name"]
Expand All @@ -731,7 +750,7 @@ def load_database_servers(input_file, selected_servers,

new_server.port = obj.get("Port", None)

new_server.username = obj.get("Username", None)
new_server.username = username

new_server.role = obj.get("Role", None)

Expand Down Expand Up @@ -785,9 +804,9 @@ def load_database_servers(input_file, selected_servers,
new_server.tunnel_keep_alive = \
obj.get("TunnelKeepAlive", None)

new_server.shared = obj.get("Shared", None)
new_server.shared = is_shared

new_server.shared_username = obj.get("SharedUsername", None)
new_server.shared_username = shared_username

Comment thread
KijongHan marked this conversation as resolved.
new_server.kerberos_conn = obj.get("KerberosAuthentication", None)

Expand Down
Loading