Skip to content

Commit

Permalink
Add cmd line user ops (#19959)
Browse files Browse the repository at this point in the history
* Add create-user/update-password commands

* Fix #19958: Add create user option to openmetadata-ops.sh

(cherry picked from commit a41c14c)
  • Loading branch information
harshach authored and OpenMetadata Release Bot committed Feb 25, 2025
1 parent 17ae9a4 commit a18e686
Show file tree
Hide file tree
Showing 3 changed files with 158 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import static org.openmetadata.common.utils.CommonUtil.nullOrEmpty;
import static org.openmetadata.service.Entity.ADMIN_USER_NAME;
import static org.openmetadata.service.Entity.FIELD_OWNERS;
import static org.openmetadata.service.Entity.ORGANIZATION_NAME;
import static org.openmetadata.service.apps.bundles.insights.utils.TimestampUtils.timestampToString;
import static org.openmetadata.service.formatter.decorators.MessageDecorator.getDateStringEpochMilli;
import static org.openmetadata.service.jdbi3.UserRepository.AUTH_MECHANISM_FIELD;
Expand All @@ -12,6 +13,7 @@

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
Expand Down Expand Up @@ -54,11 +56,13 @@
import org.openmetadata.schema.entity.applications.configuration.internal.BackfillConfiguration;
import org.openmetadata.schema.entity.applications.configuration.internal.DataInsightsAppConfig;
import org.openmetadata.schema.entity.services.ingestionPipelines.IngestionPipeline;
import org.openmetadata.schema.entity.teams.Role;
import org.openmetadata.schema.entity.teams.User;
import org.openmetadata.schema.services.connections.metadata.AuthProvider;
import org.openmetadata.schema.settings.Settings;
import org.openmetadata.schema.settings.SettingsType;
import org.openmetadata.schema.system.EventPublisherJob;
import org.openmetadata.schema.type.EntityReference;
import org.openmetadata.schema.type.Include;
import org.openmetadata.sdk.PipelineServiceClientInterface;
import org.openmetadata.service.Entity;
Expand All @@ -77,7 +81,10 @@
import org.openmetadata.service.jdbi3.IngestionPipelineRepository;
import org.openmetadata.service.jdbi3.ListFilter;
import org.openmetadata.service.jdbi3.MigrationDAO;
import org.openmetadata.service.jdbi3.PolicyRepository;
import org.openmetadata.service.jdbi3.RoleRepository;
import org.openmetadata.service.jdbi3.SystemRepository;
import org.openmetadata.service.jdbi3.TeamRepository;
import org.openmetadata.service.jdbi3.TypeRepository;
import org.openmetadata.service.jdbi3.UserRepository;
import org.openmetadata.service.jdbi3.locator.ConnectionType;
Expand Down Expand Up @@ -257,6 +264,78 @@ public Integer deleteApp(
}
}

@Command(
name = "create-user",
description = "Creates a new user when basic authentication is enabled.")
public Integer createUser(
@Option(
names = {"-u", "--user"},
description = "User email address",
required = true)
String email,
@Option(
names = {"-p", "--password"},
description = "User password",
interactive = true,
arity = "0..1",
required = true)
char[] password,
@Option(
names = {"--admin"},
description = "Promote the user as an admin",
defaultValue = "false")
boolean admin) {
try {
LOG.info("Creating user: {}", email);
if (nullOrEmpty(password)) {
throw new IllegalArgumentException("Password cannot be empty.");
}
if (nullOrEmpty(email)
|| !email.matches("^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$")) {
throw new IllegalArgumentException("Invalid email address: " + email);
}
parseConfig();
AuthProvider authProvider = config.getAuthenticationConfiguration().getProvider();
if (!authProvider.equals(AuthProvider.BASIC)) {
LOG.error("Authentication is not set to basic. User creation is not supported.");
return 1;
}
initializeCollectionRegistry();
UserRepository userRepository = (UserRepository) Entity.getEntityRepository(Entity.USER);
try {
userRepository.getByEmail(null, email, EntityUtil.Fields.EMPTY_FIELDS);
LOG.error("User {} already exists.", email);
return 1;
} catch (EntityNotFoundException ex) {
// Expected – continue to create the user.
}
initOrganization();
String domain = email.substring(email.indexOf("@") + 1);
String username = email.substring(0, email.indexOf("@"));
UserUtil.createOrUpdateUser(authProvider, username, new String(password), domain, admin);
LOG.info("User {} created successfully. Admin: {}", email, admin);
return 0;
} catch (Exception e) {
LOG.error("Failed to create user: {}", email, e);
return 1;
}
}

private void initializeCollectionRegistry() {
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
ch.qos.logback.classic.Logger rootLogger =
loggerContext.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
Level originalLevel = rootLogger.getLevel();

try {
rootLogger.setLevel(Level.ERROR);
CollectionRegistry.initialize();
} finally {
// Restore the original logging level.
rootLogger.setLevel(originalLevel);
}
}

private boolean isAppInstalled(AppRepository appRepository, String appName) {
try {
appRepository.findByName(appName, Include.NON_DELETED);
Expand Down Expand Up @@ -370,6 +449,7 @@ public Integer resetUserPassword(
}
parseConfig();
CollectionRegistry.initialize();

AuthProvider authProvider = config.getAuthenticationConfiguration().getProvider();

// Only Basic Auth provider is supported for password reset
Expand Down Expand Up @@ -999,6 +1079,42 @@ private void validateAndRunSystemDataMigrations(boolean force) {
workflow.runMigrationWorkflows();
}

private void initOrganization() {
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
ch.qos.logback.classic.Logger rootLogger =
loggerContext.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
Level originalLevel = rootLogger.getLevel();
TeamRepository teamRepository = (TeamRepository) Entity.getEntityRepository(Entity.TEAM);
try {
teamRepository.getByName(null, ORGANIZATION_NAME, EntityUtil.Fields.EMPTY_FIELDS);
} catch (EntityNotFoundException e) {
try {
PolicyRepository policyRepository =
(PolicyRepository) Entity.getEntityRepository(Entity.POLICY);
policyRepository.initSeedDataFromResources();
RoleRepository roleRepository = (RoleRepository) Entity.getEntityRepository(Entity.ROLE);
List<Role> roles = roleRepository.getEntitiesFromSeedData();
for (Role role : roles) {
role.setFullyQualifiedName(role.getName());
List<EntityReference> policies = role.getPolicies();
for (EntityReference policy : policies) {
EntityReference ref =
Entity.getEntityReferenceByName(
Entity.POLICY, policy.getName(), Include.NON_DELETED);
policy.setId(ref.getId());
}
roleRepository.initializeEntity(role);
}
teamRepository.initOrganization();
} catch (Exception ex) {
LOG.error("Failed to initialize organization due to ", ex);
throw new RuntimeException(ex);
} finally {
rootLogger.setLevel(originalLevel);
}
}
}

public static void printToAsciiTable(
List<String> columns, List<List<String>> rows, String emptyText) {
LOG.info(new AsciiTable(columns, rows, true, "", emptyText).render());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
import org.openmetadata.schema.services.connections.metadata.AuthProvider;
import org.openmetadata.schema.type.EntityReference;
import org.openmetadata.schema.utils.EntityInterfaceUtil;
import org.openmetadata.sdk.exception.UserCreationException;
import org.openmetadata.service.Entity;
import org.openmetadata.service.exception.EntityNotFoundException;
import org.openmetadata.service.jdbi3.EntityRepository;
Expand Down Expand Up @@ -81,7 +82,7 @@ public static void addUsers(
}
}

private static void createOrUpdateUser(
public static void createOrUpdateUser(
AuthProvider authProvider, String username, String password, String domain, Boolean isAdmin) {
UserRepository userRepository = (UserRepository) Entity.getEntityRepository(Entity.USER);
User updatedUser = null;
Expand Down Expand Up @@ -167,8 +168,8 @@ public static User addOrUpdateUser(User user) {
// In HA set up the other server may have already added the user.
LOG.debug("Caught exception", exception);
user.setAuthenticationMechanism(null);
throw UserCreationException.byMessage(user.getName(), exception.getMessage());
}
return null;
}

public static User user(String name, String domain, String updatedBy) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package org.openmetadata.sdk.exception;

import javax.ws.rs.core.Response;

public class UserCreationException extends WebServiceException {
private static final String BY_NAME_MESSAGE = "User Creation Exception [%s] due to [%s].";
private static final String ERROR_TYPE = "USER_CREATION_EXCEPTION";

public UserCreationException(String message) {
super(Response.Status.INTERNAL_SERVER_ERROR, ERROR_TYPE, message);
}

private UserCreationException(Response.Status status, String message) {
super(status, ERROR_TYPE, message);
}

public UserCreationException(Response.Status status, String errorType, String message) {
super(status, errorType, message);
}

public static UserCreationException byMessage(
String name, String errorMessage, Response.Status status) {
return new UserCreationException(status, buildMessageByName(name, errorMessage));
}

public static UserCreationException byMessage(
String name, String errorType, String errorMessage, Response.Status status) {
return new UserCreationException(status, errorType, buildMessageByName(name, errorMessage));
}

public static UserCreationException byMessage(String name, String errorMessage) {
return new UserCreationException(
Response.Status.BAD_REQUEST, buildMessageByName(name, errorMessage));
}

private static String buildMessageByName(String name, String errorMessage) {
return String.format(BY_NAME_MESSAGE, name, errorMessage);
}
}

0 comments on commit a18e686

Please sign in to comment.