Skip to content

Commit 0f943b5

Browse files
committed
Load predefined users from a JSON file through command line. #9229
1 parent 965a27d commit 0f943b5

2 files changed

Lines changed: 194 additions & 0 deletions

File tree

docs/en_US/user_management.rst

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,50 @@ username/email address.
270270
/path/to/python /path/to/setup.py get-users --username user1@gmail.com
271271
272272
273+
Load Users
274+
**********
275+
276+
To bulk import users from a JSON file, invoke ``setup.py`` with ``load-users`` command line option,
277+
followed by the path to the JSON file.
278+
279+
.. code-block:: bash
280+
281+
/path/to/python /path/to/setup.py load-users /path/to/users.json
282+
283+
**JSON File Format**
284+
285+
The input JSON file must contain a ``users`` array with user objects:
286+
287+
.. code-block:: json
288+
289+
{
290+
"users": [
291+
{
292+
"username": "admin@example.com",
293+
"email": "admin@example.com",
294+
"password": "securepassword",
295+
"role": "Administrator",
296+
"active": true,
297+
"auth_source": "internal"
298+
},
299+
{
300+
"username": "ldap_user",
301+
"email": "ldap_user@example.com",
302+
"role": "User",
303+
"active": true,
304+
"auth_source": "ldap"
305+
}
306+
]
307+
}
308+
309+
The command handles errors gracefully:
310+
311+
* Users that already exist are skipped
312+
* Invalid roles are reported and skipped
313+
* Missing passwords for internal auth are reported and skipped
314+
* Passwords shorter than 6 characters are reported and skipped
315+
316+
273317
Output
274318
******
275319

web/setup.py

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,156 @@ def get_role(role: str):
163163

164164
class ManageUsers:
165165

166+
@app.command()
167+
@update_sqlite_path
168+
def load_users(input_file: str,
169+
sqlite_path: Optional[str] = None,
170+
json: Optional[bool] = False):
171+
"""Load users from a JSON file.
172+
173+
Expected JSON format:
174+
{
175+
"users": [
176+
{
177+
"username": "user@example.com",
178+
"email": "user@example.com",
179+
"password": "password123",
180+
"role": "User",
181+
"active": true,
182+
"auth_source": "internal"
183+
},
184+
{
185+
"username": "ldap_user",
186+
"email": "ldap@example.com",
187+
"role": "Administrator",
188+
"active": true,
189+
"auth_source": "ldap"
190+
}
191+
]
192+
}
193+
"""
194+
from urllib.parse import unquote
195+
196+
print('----------')
197+
print('Loading users from:', input_file)
198+
print('SQLite pgAdmin config:', config.SQLITE_PATH)
199+
print('----------')
200+
201+
# Parse the input file path
202+
try:
203+
file_path = unquote(input_file)
204+
except Exception as e:
205+
print(str(e))
206+
return _handle_error(str(e), True)
207+
208+
# Read and parse JSON file
209+
try:
210+
with open(file_path) as f:
211+
data = jsonlib.load(f)
212+
except jsonlib.decoder.JSONDecodeError as e:
213+
return _handle_error(
214+
gettext("Error parsing input file %s: %s" % (file_path, e)),
215+
True)
216+
except Exception as e:
217+
return _handle_error(
218+
gettext("Error reading input file %s: [%d] %s" %
219+
(file_path, e.errno, e.strerror)), True)
220+
221+
# Validate JSON structure
222+
if 'users' not in data:
223+
return _handle_error(
224+
gettext("Invalid JSON format: 'users' key not found"), True)
225+
226+
users_data = data['users']
227+
if not isinstance(users_data, list):
228+
return _handle_error(
229+
gettext("Invalid JSON format: 'users' must be a list"), True)
230+
231+
created_count = 0
232+
skipped_count = 0
233+
error_count = 0
234+
235+
app = create_app(config.APP_NAME + '-cli')
236+
with app.test_request_context():
237+
for user_entry in users_data:
238+
try:
239+
# Validate required fields
240+
if 'username' not in user_entry and 'email' not in user_entry:
241+
print(f"Skipping user: missing 'username' or 'email'")
242+
error_count += 1
243+
continue
244+
245+
# Determine auth_source (default to internal)
246+
auth_source = user_entry.get('auth_source', INTERNAL)
247+
248+
# Build user data dict
249+
user_data = {
250+
'username': user_entry.get('username',
251+
user_entry.get('email')),
252+
'email': user_entry.get('email'),
253+
'role': user_entry.get('role', 'User'),
254+
'active': user_entry.get('active', True),
255+
'auth_source': auth_source
256+
}
257+
258+
# For internal auth, password is required
259+
if auth_source == INTERNAL:
260+
if 'password' not in user_entry:
261+
print(f"Skipping user '{user_data['username']}': "
262+
f"password required for internal auth")
263+
error_count += 1
264+
continue
265+
user_data['newPassword'] = user_entry['password']
266+
user_data['confirmPassword'] = user_entry['password']
267+
268+
# Check if user already exists
269+
uid = ManageUsers.get_user(
270+
username=user_data['username'],
271+
auth_source=auth_source)
272+
if uid:
273+
print(f"Skipping user '{user_data['username']}': "
274+
f"already exists")
275+
skipped_count += 1
276+
continue
277+
278+
# Get role ID
279+
rid = ManageRoles.get_role(user_data['role'])
280+
if rid is None:
281+
print(f"Skipping user '{user_data['username']}': "
282+
f"role '{user_data['role']}' does not exist")
283+
error_count += 1
284+
continue
285+
286+
user_data['role'] = rid
287+
288+
# Validate password length for internal users
289+
if auth_source == INTERNAL:
290+
if len(user_data['newPassword']) < 6:
291+
print(f"Skipping user '{user_data['username']}': "
292+
f"password must be at least 6 characters")
293+
error_count += 1
294+
continue
295+
296+
# Create the user
297+
status, msg = create_user(user_data)
298+
if status:
299+
print(f"Created user: {user_data['username']}")
300+
created_count += 1
301+
else:
302+
print(f"Error creating user '{user_data['username']}'"
303+
f": {msg}")
304+
error_count += 1
305+
306+
except Exception as e:
307+
print(f"Error processing user entry: {str(e)}")
308+
error_count += 1
309+
310+
print('----------')
311+
print(f"Users created: {created_count}")
312+
print(f"Users skipped (already exist): {skipped_count}")
313+
print(f"Errors: {error_count}")
314+
print('----------')
315+
166316
@app.command()
167317
@update_sqlite_path
168318
def add_user(email: str, password: str,

0 commit comments

Comments
 (0)