mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-12-29 00:18:24 +00:00
* Fix #8958: Add initialization ordering and remove user/bot initialization from Authorizer * Refactor EmailUtil initialization * Fix config values * Fix smtpsettings check * Fix uninitialize EmailUtil * remove unused USER_REPOSITORY Co-authored-by: mohitdeuex <mohit.y@deuexsolutions.com> Co-authored-by: Mohit Yadav <105265192+mohityadav766@users.noreply.github.com>
This commit is contained in:
parent
f00ede11bf
commit
8a95ec1329
@ -74,7 +74,6 @@ import org.openmetadata.service.jdbi3.locator.ConnectionAwareAnnotationSqlLocato
|
||||
import org.openmetadata.service.migration.Migration;
|
||||
import org.openmetadata.service.migration.MigrationConfiguration;
|
||||
import org.openmetadata.service.resources.CollectionRegistry;
|
||||
import org.openmetadata.service.resources.tags.TagLabelCache;
|
||||
import org.openmetadata.service.secrets.SecretsManager;
|
||||
import org.openmetadata.service.secrets.SecretsManagerFactory;
|
||||
import org.openmetadata.service.secrets.SecretsManagerUpdateService;
|
||||
@ -105,9 +104,7 @@ public class OpenMetadataApplication extends Application<OpenMetadataApplication
|
||||
validateConfiguration(catalogConfig);
|
||||
|
||||
// init email Util for handling
|
||||
if (catalogConfig.getSmtpSettings() != null && catalogConfig.getSmtpSettings().getEnableSmtpServer()) {
|
||||
EmailUtil.EmailUtilBuilder.build(catalogConfig.getSmtpSettings());
|
||||
}
|
||||
EmailUtil.initialize(catalogConfig);
|
||||
final Jdbi jdbi = createAndSetupJDBI(environment, catalogConfig.getDataSourceFactory());
|
||||
final SecretsManager secretsManager =
|
||||
SecretsManagerFactory.createSecretsManager(
|
||||
@ -310,7 +307,6 @@ public class OpenMetadataApplication extends Application<OpenMetadataApplication
|
||||
|
||||
private void registerResources(OpenMetadataApplicationConfig config, Environment environment, Jdbi jdbi) {
|
||||
CollectionRegistry.getInstance().registerResources(jdbi, environment, config, authorizer, authenticatorHandler);
|
||||
TagLabelCache.initialize();
|
||||
environment.jersey().register(new JsonPatchProvider());
|
||||
ErrorPageErrorHandler eph = new ErrorPageErrorHandler();
|
||||
eph.addErrorPage(Response.Status.NOT_FOUND.getStatusCode(), "/");
|
||||
|
||||
@ -14,6 +14,7 @@
|
||||
package org.openmetadata.service.jdbi3;
|
||||
|
||||
import java.io.IOException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.openmetadata.schema.entity.Bot;
|
||||
import org.openmetadata.schema.entity.teams.User;
|
||||
import org.openmetadata.schema.type.EntityReference;
|
||||
@ -23,6 +24,7 @@ import org.openmetadata.service.Entity;
|
||||
import org.openmetadata.service.resources.bots.BotResource;
|
||||
import org.openmetadata.service.util.EntityUtil.Fields;
|
||||
|
||||
@Slf4j
|
||||
public class BotRepository extends EntityRepository<Bot> {
|
||||
|
||||
static final String BOT_UPDATE_FIELDS = "botUser";
|
||||
|
||||
@ -27,6 +27,7 @@ import java.util.stream.Collectors;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.openmetadata.schema.api.teams.CreateTeam.TeamType;
|
||||
import org.openmetadata.schema.auth.SSOAuthMechanism;
|
||||
import org.openmetadata.schema.entity.teams.AuthenticationMechanism;
|
||||
import org.openmetadata.schema.entity.teams.Team;
|
||||
import org.openmetadata.schema.entity.teams.User;
|
||||
@ -34,15 +35,18 @@ import org.openmetadata.schema.type.EntityReference;
|
||||
import org.openmetadata.schema.type.Include;
|
||||
import org.openmetadata.schema.type.Relationship;
|
||||
import org.openmetadata.service.Entity;
|
||||
import org.openmetadata.service.OpenMetadataApplicationConfig;
|
||||
import org.openmetadata.service.exception.CatalogExceptionMessage;
|
||||
import org.openmetadata.service.jdbi3.CollectionDAO.EntityRelationshipRecord;
|
||||
import org.openmetadata.service.resources.teams.UserResource;
|
||||
import org.openmetadata.service.secrets.SecretsManager;
|
||||
import org.openmetadata.service.secrets.SecretsManagerFactory;
|
||||
import org.openmetadata.service.security.SecurityUtil;
|
||||
import org.openmetadata.service.security.policyevaluator.SubjectCache;
|
||||
import org.openmetadata.service.util.EntityUtil;
|
||||
import org.openmetadata.service.util.EntityUtil.Fields;
|
||||
import org.openmetadata.service.util.JsonUtils;
|
||||
import org.openmetadata.service.util.UserUtil;
|
||||
|
||||
@Slf4j
|
||||
public class UserRepository extends EntityRepository<User> {
|
||||
@ -181,6 +185,22 @@ public class UserRepository extends EntityRepository<User> {
|
||||
return daoCollection.userDAO().checkEmailExists(emailId) > 0;
|
||||
}
|
||||
|
||||
public void initializeUsers(OpenMetadataApplicationConfig config) {
|
||||
Set<String> adminUsers = new HashSet<>(config.getAuthorizerConfiguration().getAdminPrincipals());
|
||||
LOG.debug("Checking user entries for admin users {}", adminUsers);
|
||||
String domain = SecurityUtil.getDomain(config);
|
||||
String providerType = config.getAuthenticationConfiguration().getProvider();
|
||||
if (providerType.equals(SSOAuthMechanism.SsoServiceType.BASIC.value())) {
|
||||
UserUtil.handleBasicAuth(adminUsers, domain);
|
||||
} else {
|
||||
UserUtil.addUsers(adminUsers, domain, true);
|
||||
}
|
||||
|
||||
LOG.debug("Checking user entries for test users");
|
||||
Set<String> testUsers = new HashSet<>(config.getAuthorizerConfiguration().getTestPrincipals());
|
||||
UserUtil.addUsers(testUsers, domain, null);
|
||||
}
|
||||
|
||||
private List<EntityReference> getOwns(User user) throws IOException {
|
||||
// Compile entities owned by the user
|
||||
List<EntityRelationshipRecord> ownedEntities =
|
||||
|
||||
@ -13,8 +13,6 @@
|
||||
|
||||
package org.openmetadata.service.resources.bots;
|
||||
|
||||
import static org.openmetadata.service.security.DefaultAuthorizer.user;
|
||||
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.v3.oas.annotations.ExternalDocumentation;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
@ -67,18 +65,18 @@ import org.openmetadata.service.resources.Collection;
|
||||
import org.openmetadata.service.resources.EntityResource;
|
||||
import org.openmetadata.service.resources.teams.RoleResource;
|
||||
import org.openmetadata.service.security.Authorizer;
|
||||
import org.openmetadata.service.security.DefaultAuthorizer;
|
||||
import org.openmetadata.service.security.SecurityUtil;
|
||||
import org.openmetadata.service.util.EntityUtil;
|
||||
import org.openmetadata.service.util.EntityUtil.Fields;
|
||||
import org.openmetadata.service.util.ResultList;
|
||||
import org.openmetadata.service.util.UserUtil;
|
||||
|
||||
@Slf4j
|
||||
@Path("/v1/bots")
|
||||
@Api(value = "Bot collection", tags = "Bot collection")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Collection(name = "bots", order = 8) // initialize after user resource
|
||||
@Collection(name = "bots", order = 4) // initialize after user resource
|
||||
public class BotResource extends EntityResource<Bot, BotRepository> {
|
||||
public static final String COLLECTION_PATH = "/v1/bots/";
|
||||
|
||||
@ -93,12 +91,12 @@ public class BotResource extends EntityResource<Bot, BotRepository> {
|
||||
String domain = SecurityUtil.getDomain(config);
|
||||
for (Bot bot : bots) {
|
||||
String userName = bot.getBotUser().getName();
|
||||
User user = user(userName, domain, userName).withIsBot(true).withIsAdmin(false);
|
||||
User user = UserUtil.user(userName, domain, userName).withIsBot(true).withIsAdmin(false);
|
||||
|
||||
// Add role corresponding to the bot to the user
|
||||
// we need to set a mutable list here
|
||||
user.setRoles(Arrays.asList(RoleResource.getRole(getRoleForBot(bot.getName()))));
|
||||
user = DefaultAuthorizer.addOrUpdateBotUser(user, config);
|
||||
user = UserUtil.addOrUpdateBotUser(user, config);
|
||||
|
||||
bot.withId(UUID.randomUUID())
|
||||
.withBotUser(user.getEntityReference())
|
||||
|
||||
@ -64,7 +64,7 @@ import org.openmetadata.service.util.ResultList;
|
||||
@Api(value = "Glossary collection", tags = "Glossary collection")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Collection(name = "glossaries")
|
||||
@Collection(name = "glossaries", order = 4)
|
||||
public class GlossaryResource extends EntityResource<Glossary, GlossaryRepository> {
|
||||
public static final String COLLECTION_PATH = "v1/glossaries/";
|
||||
|
||||
|
||||
@ -68,7 +68,7 @@ import org.openmetadata.service.util.ResultList;
|
||||
@Api(value = "Glossary collection", tags = "Glossary collection")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Collection(name = "glossaries")
|
||||
@Collection(name = "glossaries", order = 5)
|
||||
public class GlossaryTermResource extends EntityResource<GlossaryTerm, GlossaryTermRepository> {
|
||||
public static final String COLLECTION_PATH = "v1/glossaryTerms/";
|
||||
|
||||
|
||||
@ -102,6 +102,7 @@ public class PolicyResource extends EntityResource<Policy, PolicyRepository> {
|
||||
// Load any existing rules from database, before loading seed data.
|
||||
dao.initSeedDataFromResources();
|
||||
ResourceRegistry.initialize(listOrEmpty(PolicyResource.getResourceDescriptors()));
|
||||
PolicyCache.initialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -75,7 +75,7 @@ import org.openmetadata.service.util.ResultList;
|
||||
@Api(value = "Tags resources collection", tags = "Tags resources collection")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Collection(name = "tags")
|
||||
@Collection(name = "tags", order = 6) // initialize after Glossary and GlossaryTerm
|
||||
public class TagResource {
|
||||
public static final String TAG_COLLECTION_PATH = "/v1/tags/";
|
||||
private final TagRepository dao;
|
||||
@ -119,6 +119,7 @@ public class TagResource {
|
||||
.withProvider(tagCategory.getProvider()));
|
||||
});
|
||||
daoCategory.initCategory(tagCategory);
|
||||
TagLabelCache.initialize();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -102,6 +102,7 @@ public class RoleResource extends EntityResource<Role, RoleRepository> {
|
||||
}
|
||||
dao.initializeEntity(role);
|
||||
}
|
||||
RoleCache.initialize();
|
||||
}
|
||||
|
||||
public static class RoleList extends ResultList<Role> {
|
||||
|
||||
@ -71,7 +71,7 @@ import org.openmetadata.service.util.ResultList;
|
||||
@Api(value = "Teams collection", tags = "Teams collection")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Collection(name = "teams", order = 3) // Load after roles, and policy resources
|
||||
@Collection(name = "teams", order = 2) // Load after roles, and policy resources
|
||||
public class TeamResource extends EntityResource<Team, TeamRepository> {
|
||||
public static final String COLLECTION_PATH = "/v1/teams/";
|
||||
|
||||
|
||||
@ -110,6 +110,7 @@ import org.openmetadata.service.security.auth.BotTokenCache;
|
||||
import org.openmetadata.service.security.jwt.JWTTokenGenerator;
|
||||
import org.openmetadata.service.security.policyevaluator.OperationContext;
|
||||
import org.openmetadata.service.security.policyevaluator.ResourceContext;
|
||||
import org.openmetadata.service.security.policyevaluator.SubjectCache;
|
||||
import org.openmetadata.service.security.saml.JwtTokenCacheManager;
|
||||
import org.openmetadata.service.util.EmailUtil;
|
||||
import org.openmetadata.service.util.EntityUtil;
|
||||
@ -124,7 +125,7 @@ import org.openmetadata.service.util.ResultList;
|
||||
@Api(value = "User collection", tags = "User collection")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Collection(name = "users", order = 7) // Initialize user resource before bot resource (at default order 9)
|
||||
@Collection(name = "users", order = 3) // Initialize user resource before bot resource (at default order 9)
|
||||
public class UserResource extends EntityResource<User, UserRepository> {
|
||||
public static final String COLLECTION_PATH = "v1/users/";
|
||||
public static final String USER_PROTECTED_FIELDS = "authenticationMechanism";
|
||||
@ -157,6 +158,8 @@ public class UserResource extends EntityResource<User, UserRepository> {
|
||||
this.authenticationConfiguration = config.getAuthenticationConfiguration();
|
||||
SmtpSettings smtpSettings = config.getSmtpSettings();
|
||||
this.isEmailServiceEnabled = smtpSettings != null && smtpSettings.getEnableSmtpServer();
|
||||
this.dao.initializeUsers(config);
|
||||
SubjectCache.initialize();
|
||||
}
|
||||
|
||||
public static class UserList extends ResultList<User> {
|
||||
|
||||
@ -13,171 +13,28 @@
|
||||
|
||||
package org.openmetadata.service.security;
|
||||
|
||||
import static org.openmetadata.common.utils.CommonUtil.nullOrEmpty;
|
||||
import static org.openmetadata.schema.auth.SSOAuthMechanism.SsoServiceType.AUTH_0;
|
||||
import static org.openmetadata.schema.auth.SSOAuthMechanism.SsoServiceType.AZURE;
|
||||
import static org.openmetadata.schema.auth.SSOAuthMechanism.SsoServiceType.CUSTOM_OIDC;
|
||||
import static org.openmetadata.schema.auth.SSOAuthMechanism.SsoServiceType.GOOGLE;
|
||||
import static org.openmetadata.schema.auth.SSOAuthMechanism.SsoServiceType.OKTA;
|
||||
import static org.openmetadata.schema.entity.teams.AuthenticationMechanism.AuthType.JWT;
|
||||
import static org.openmetadata.schema.entity.teams.AuthenticationMechanism.AuthType.SSO;
|
||||
import static org.openmetadata.schema.type.Permission.Access.ALLOW;
|
||||
import static org.openmetadata.service.Entity.ADMIN_USER_NAME;
|
||||
import static org.openmetadata.service.exception.CatalogExceptionMessage.notAdmin;
|
||||
import static org.openmetadata.service.resources.teams.UserResource.USER_PROTECTED_FIELDS;
|
||||
|
||||
import at.favre.lib.crypto.bcrypt.BCrypt;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import javax.ws.rs.core.SecurityContext;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jdbi.v3.core.Jdbi;
|
||||
import org.openmetadata.schema.api.configuration.airflow.AirflowConfiguration;
|
||||
import org.openmetadata.schema.api.security.AuthenticationConfiguration;
|
||||
import org.openmetadata.schema.auth.BasicAuthMechanism;
|
||||
import org.openmetadata.schema.auth.JWTAuthMechanism;
|
||||
import org.openmetadata.schema.auth.JWTTokenExpiry;
|
||||
import org.openmetadata.schema.auth.SSOAuthMechanism;
|
||||
import org.openmetadata.schema.email.SmtpSettings;
|
||||
import org.openmetadata.schema.entity.teams.AuthenticationMechanism;
|
||||
import org.openmetadata.schema.entity.teams.User;
|
||||
import org.openmetadata.schema.security.client.OpenMetadataJWTClientConfig;
|
||||
import org.openmetadata.schema.type.ResourcePermission;
|
||||
import org.openmetadata.service.Entity;
|
||||
import org.openmetadata.service.OpenMetadataApplicationConfig;
|
||||
import org.openmetadata.service.exception.EntityNotFoundException;
|
||||
import org.openmetadata.service.jdbi3.EntityRepository;
|
||||
import org.openmetadata.service.jdbi3.UserRepository;
|
||||
import org.openmetadata.service.security.jwt.JWTTokenGenerator;
|
||||
import org.openmetadata.service.security.policyevaluator.OperationContext;
|
||||
import org.openmetadata.service.security.policyevaluator.PolicyCache;
|
||||
import org.openmetadata.service.security.policyevaluator.PolicyEvaluator;
|
||||
import org.openmetadata.service.security.policyevaluator.ResourceContextInterface;
|
||||
import org.openmetadata.service.security.policyevaluator.RoleCache;
|
||||
import org.openmetadata.service.security.policyevaluator.SubjectCache;
|
||||
import org.openmetadata.service.security.policyevaluator.SubjectContext;
|
||||
import org.openmetadata.service.util.EmailUtil;
|
||||
import org.openmetadata.service.util.EntityUtil;
|
||||
import org.openmetadata.service.util.PasswordUtil;
|
||||
import org.openmetadata.service.util.RestUtil;
|
||||
|
||||
@Slf4j
|
||||
public class DefaultAuthorizer implements Authorizer {
|
||||
private static final String COLON_DELIMITER = ":";
|
||||
private boolean isSmtpEnabled;
|
||||
|
||||
@Override
|
||||
public void init(OpenMetadataApplicationConfig config, Jdbi dbi) {
|
||||
LOG.info("Initializing DefaultAuthorizer with config {}", config.getAuthorizerConfiguration());
|
||||
SmtpSettings smtpSettings = config.getSmtpSettings();
|
||||
this.isSmtpEnabled = smtpSettings != null && smtpSettings.getEnableSmtpServer();
|
||||
SubjectCache.initialize();
|
||||
PolicyCache.initialize();
|
||||
RoleCache.initialize();
|
||||
initializeUsers(config);
|
||||
}
|
||||
|
||||
private void initializeUsers(OpenMetadataApplicationConfig config) {
|
||||
Set<String> adminUsers = new HashSet<>(config.getAuthorizerConfiguration().getAdminPrincipals());
|
||||
LOG.debug("Checking user entries for admin users {}", adminUsers);
|
||||
String domain = SecurityUtil.getDomain(config);
|
||||
String providerType = config.getAuthenticationConfiguration().getProvider();
|
||||
if (providerType.equals(SSOAuthMechanism.SsoServiceType.BASIC.value())) {
|
||||
handleBasicAuth(adminUsers, domain);
|
||||
} else {
|
||||
addUsers(adminUsers, domain, true);
|
||||
}
|
||||
|
||||
LOG.debug("Checking user entries for test users");
|
||||
Set<String> testUsers = new HashSet<>(config.getAuthorizerConfiguration().getTestPrincipals());
|
||||
addUsers(testUsers, domain, null);
|
||||
}
|
||||
|
||||
private void handleBasicAuth(Set<String> adminUsers, String domain) {
|
||||
try {
|
||||
for (String adminUser : adminUsers) {
|
||||
if (adminUser.contains(COLON_DELIMITER)) {
|
||||
String[] tokens = adminUser.split(COLON_DELIMITER);
|
||||
addUserForBasicAuth(tokens[0], tokens[1], domain);
|
||||
} else {
|
||||
boolean isDefaultAdmin = adminUser.equals(ADMIN_USER_NAME);
|
||||
String token = PasswordUtil.generateRandomPassword();
|
||||
if (isDefaultAdmin || !isSmtpEnabled) {
|
||||
token = ADMIN_USER_NAME;
|
||||
}
|
||||
addUserForBasicAuth(adminUser, token, domain);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.error("Failed in Basic Auth Setup. Reason : {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void addUserForBasicAuth(String username, String pwd, String domain) throws IOException {
|
||||
EntityRepository<User> userRepository = Entity.getEntityRepository(Entity.USER);
|
||||
User originalUser;
|
||||
try {
|
||||
List<String> fields = userRepository.getAllowedFieldsCopy();
|
||||
fields.add(USER_PROTECTED_FIELDS);
|
||||
originalUser = userRepository.getByName(null, username, new EntityUtil.Fields(fields, String.join(",", fields)));
|
||||
if (originalUser.getAuthenticationMechanism() == null) {
|
||||
updateUserWithHashedPwd(originalUser, pwd);
|
||||
}
|
||||
addOrUpdateUser(originalUser);
|
||||
} catch (EntityNotFoundException e) {
|
||||
// TODO: Not the best way ! :(
|
||||
User user = user(username, domain, username).withIsAdmin(true).withIsEmailVerified(true);
|
||||
updateUserWithHashedPwd(user, pwd);
|
||||
addOrUpdateUser(user);
|
||||
if (isSmtpEnabled) {
|
||||
sendInviteMailToAdmin(user, pwd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void sendInviteMailToAdmin(User user, String pwd) {
|
||||
Map<String, String> templatePopulator = new HashMap<>();
|
||||
templatePopulator.put(EmailUtil.ENTITY, EmailUtil.getInstance().getEmailingEntity());
|
||||
templatePopulator.put(EmailUtil.SUPPORT_URL, EmailUtil.getInstance().getSupportUrl());
|
||||
templatePopulator.put(EmailUtil.USERNAME, user.getName());
|
||||
templatePopulator.put(EmailUtil.PASSWORD, pwd);
|
||||
templatePopulator.put(EmailUtil.APPLICATION_LOGIN_LINK, EmailUtil.getInstance().getOMUrl());
|
||||
try {
|
||||
EmailUtil.getInstance()
|
||||
.sendMail(
|
||||
EmailUtil.getInstance().getEmailInviteSubject(),
|
||||
templatePopulator,
|
||||
user.getEmail(),
|
||||
EmailUtil.EMAIL_TEMPLATE_BASEPATH,
|
||||
EmailUtil.INVITE_RANDOM_PWD);
|
||||
} catch (Exception ex) {
|
||||
LOG.error("Failed in sending Mail to user [{}]. Reason : {}", user.getEmail(), ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void updateUserWithHashedPwd(User user, String pwd) {
|
||||
String hashedPwd = BCrypt.withDefaults().hashToString(12, pwd.toCharArray());
|
||||
user.setAuthenticationMechanism(
|
||||
new AuthenticationMechanism()
|
||||
.withAuthType(AuthenticationMechanism.AuthType.BASIC)
|
||||
.withConfig(new BasicAuthMechanism().withPassword(hashedPwd)));
|
||||
}
|
||||
|
||||
public static User user(String name, String domain, String updatedBy) {
|
||||
return new User()
|
||||
.withId(UUID.randomUUID())
|
||||
.withName(name)
|
||||
.withFullyQualifiedName(name)
|
||||
.withEmail(name + "@" + domain)
|
||||
.withUpdatedBy(updatedBy)
|
||||
.withUpdatedAt(System.currentTimeMillis())
|
||||
.withIsBot(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -234,130 +91,6 @@ public class DefaultAuthorizer implements Authorizer {
|
||||
return subjectContext.isAdmin() || subjectContext.isBot();
|
||||
}
|
||||
|
||||
private void addUsers(Set<String> users, String domain, Boolean isAdmin) {
|
||||
for (String userName : users) {
|
||||
User user = user(userName, domain, userName).withIsAdmin(isAdmin);
|
||||
addOrUpdateUser(user);
|
||||
}
|
||||
}
|
||||
|
||||
private static User addOrUpdateUser(User user) {
|
||||
EntityRepository<User> userRepository = Entity.getEntityRepository(Entity.USER);
|
||||
try {
|
||||
RestUtil.PutResponse<User> addedUser = userRepository.createOrUpdate(null, user);
|
||||
// should not log the user auth details in LOGS
|
||||
LOG.debug("Added user entry: {}", addedUser.getEntity().getName());
|
||||
return addedUser.getEntity();
|
||||
} catch (Exception exception) {
|
||||
// In HA set up the other server may have already added the user.
|
||||
LOG.debug("Caught exception", exception);
|
||||
user.setAuthenticationMechanism(null);
|
||||
LOG.debug("User entry: {} already exists.", user.getName());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method add auth mechanism in the following way:
|
||||
*
|
||||
* <ul>
|
||||
* <li>If original user has already an authMechanism, add it to the user
|
||||
* <li>Otherwise:
|
||||
* <ul>
|
||||
* <li>If airflow configuration is 'openmetadata' and server auth provider is not basic, add JWT auth
|
||||
* mechanism from Airflow configuration
|
||||
* <li>Otherwise:
|
||||
* <ul>
|
||||
* <li>If airflow configuration is 'basic', add JWT auth mechanism with a generated token which does not
|
||||
* expire
|
||||
* <li>Otherwise, add SSO auth mechanism from Airflow configuration
|
||||
* </ul>
|
||||
* </ul>
|
||||
* </ul>
|
||||
*/
|
||||
public static User addOrUpdateBotUser(User user, OpenMetadataApplicationConfig openMetadataApplicationConfig) {
|
||||
User originalUser = retrieveWithAuthMechanism(user);
|
||||
// the user did not have an auth mechanism
|
||||
AuthenticationMechanism authMechanism = originalUser != null ? originalUser.getAuthenticationMechanism() : null;
|
||||
if (authMechanism == null) {
|
||||
AuthenticationConfiguration authConfig = openMetadataApplicationConfig.getAuthenticationConfiguration();
|
||||
AirflowConfiguration airflowConfig = openMetadataApplicationConfig.getAirflowConfiguration();
|
||||
// if the auth provider is "openmetadata" in the configuration set JWT as auth mechanism
|
||||
if ("openmetadata".equals(airflowConfig.getAuthProvider()) && !"basic".equals(authConfig.getProvider())) {
|
||||
OpenMetadataJWTClientConfig jwtClientConfig = airflowConfig.getAuthConfig().getOpenmetadata();
|
||||
authMechanism = buildAuthMechanism(JWT, buildJWTAuthMechanism(jwtClientConfig, user));
|
||||
} else {
|
||||
// Otherwise, set auth mechanism from airflow configuration
|
||||
// TODO: https://github.com/open-metadata/OpenMetadata/issues/7712
|
||||
if (airflowConfig.getAuthConfig() != null && !"basic".equals(authConfig.getProvider())) {
|
||||
switch (authConfig.getProvider()) {
|
||||
case "no-auth":
|
||||
break;
|
||||
case "azure":
|
||||
authMechanism =
|
||||
buildAuthMechanism(SSO, buildAuthMechanismConfig(AZURE, airflowConfig.getAuthConfig().getAzure()));
|
||||
break;
|
||||
case "google":
|
||||
authMechanism =
|
||||
buildAuthMechanism(SSO, buildAuthMechanismConfig(GOOGLE, airflowConfig.getAuthConfig().getGoogle()));
|
||||
break;
|
||||
case "okta":
|
||||
authMechanism =
|
||||
buildAuthMechanism(SSO, buildAuthMechanismConfig(OKTA, airflowConfig.getAuthConfig().getOkta()));
|
||||
break;
|
||||
case "auth0":
|
||||
authMechanism =
|
||||
buildAuthMechanism(SSO, buildAuthMechanismConfig(AUTH_0, airflowConfig.getAuthConfig().getAuth0()));
|
||||
break;
|
||||
case "custom-oidc":
|
||||
authMechanism =
|
||||
buildAuthMechanism(
|
||||
SSO, buildAuthMechanismConfig(CUSTOM_OIDC, airflowConfig.getAuthConfig().getCustomOidc()));
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException(
|
||||
String.format(
|
||||
"Unexpected auth provider [%s] for bot [%s]", authConfig.getProvider(), user.getName()));
|
||||
}
|
||||
} else if ("basic".equals(authConfig.getProvider())) {
|
||||
authMechanism = buildAuthMechanism(JWT, buildJWTAuthMechanism(null, user));
|
||||
}
|
||||
}
|
||||
}
|
||||
user.setAuthenticationMechanism(authMechanism);
|
||||
user.setDescription(user.getDescription());
|
||||
user.setDisplayName(user.getDisplayName());
|
||||
user.setUpdatedBy(ADMIN_USER_NAME);
|
||||
return addOrUpdateUser(user);
|
||||
}
|
||||
|
||||
private static JWTAuthMechanism buildJWTAuthMechanism(OpenMetadataJWTClientConfig jwtClientConfig, User user) {
|
||||
return Objects.isNull(jwtClientConfig) || nullOrEmpty(jwtClientConfig.getJwtToken())
|
||||
? JWTTokenGenerator.getInstance().generateJWTToken(user, JWTTokenExpiry.Unlimited)
|
||||
: new JWTAuthMechanism()
|
||||
.withJWTToken(jwtClientConfig.getJwtToken())
|
||||
.withJWTTokenExpiry(JWTTokenExpiry.Unlimited);
|
||||
}
|
||||
|
||||
private static SSOAuthMechanism buildAuthMechanismConfig(
|
||||
SSOAuthMechanism.SsoServiceType ssoServiceType, Object config) {
|
||||
return new SSOAuthMechanism().withSsoServiceType(ssoServiceType).withAuthConfig(config);
|
||||
}
|
||||
|
||||
private static AuthenticationMechanism buildAuthMechanism(AuthenticationMechanism.AuthType authType, Object config) {
|
||||
return new AuthenticationMechanism().withAuthType(authType).withConfig(config);
|
||||
}
|
||||
|
||||
private static User retrieveWithAuthMechanism(User user) {
|
||||
EntityRepository<User> userRepository = UserRepository.class.cast(Entity.getEntityRepository(Entity.USER));
|
||||
try {
|
||||
return userRepository.getByName(null, user.getName(), new EntityUtil.Fields(List.of("authenticationMechanism")));
|
||||
} catch (IOException | EntityNotFoundException e) {
|
||||
LOG.debug("Bot entity: {} does not exists.", user);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static SubjectContext getSubjectContext(SecurityContext securityContext) {
|
||||
if (securityContext == null || securityContext.getUserPrincipal() == null) {
|
||||
throw new AuthenticationException("No principal in security context");
|
||||
|
||||
@ -15,11 +15,11 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.openmetadata.common.utils.CommonUtil;
|
||||
import org.openmetadata.schema.email.EmailRequest;
|
||||
import org.openmetadata.schema.email.SmtpSettings;
|
||||
import org.openmetadata.schema.entity.feed.Thread;
|
||||
import org.openmetadata.schema.entity.teams.User;
|
||||
import org.openmetadata.schema.tests.type.TestCaseResult;
|
||||
import org.openmetadata.service.OpenMetadataApplicationConfig;
|
||||
import org.simplejavamail.api.email.Email;
|
||||
import org.simplejavamail.api.email.EmailPopulatingBuilder;
|
||||
import org.simplejavamail.api.mailer.Mailer;
|
||||
@ -58,22 +58,35 @@ public class EmailUtil {
|
||||
public static final String INVITE_CREATE_PWD = "invite-createPassword.ftl";
|
||||
public static final String TASK_NOTIFICATION_TEMPLATE = "taskAssignment.ftl";
|
||||
public static final String TEST_NOTIFICATION_TEMPLATE = "testResultStatus.ftl";
|
||||
private static EmailUtil INSTANCE = null;
|
||||
private SmtpSettings defaultSmtpSettings = null;
|
||||
private Mailer mailer = null;
|
||||
private Configuration templateConfiguration = null;
|
||||
private static EmailUtil INSTANCE;
|
||||
private static SmtpSettings DEFAULT_SMTP_SETTINGS;
|
||||
private static Mailer MAILER;
|
||||
private static Configuration TEMPLATE_CONFIGURATION;
|
||||
private static volatile boolean INITIALIZED = false;
|
||||
|
||||
private EmailUtil(SmtpSettings smtpServerSettings) {
|
||||
try {
|
||||
this.defaultSmtpSettings = smtpServerSettings;
|
||||
this.mailer = this.createMailer(smtpServerSettings);
|
||||
this.templateConfiguration = new Configuration(VERSION_2_3_28);
|
||||
} catch (Exception ex) {
|
||||
LOG.warn("[MAILER] Smtp Configurations are missing : Reason {} ", ex.getMessage(), ex);
|
||||
public static void initialize(OpenMetadataApplicationConfig config) {
|
||||
if (!INITIALIZED) {
|
||||
if (config.getSmtpSettings() != null && config.getSmtpSettings().getEnableSmtpServer()) {
|
||||
try {
|
||||
DEFAULT_SMTP_SETTINGS = config.getSmtpSettings();
|
||||
MAILER = createMailer(DEFAULT_SMTP_SETTINGS);
|
||||
TEMPLATE_CONFIGURATION = new Configuration(VERSION_2_3_28);
|
||||
LOG.info("Email Util cache is initialized");
|
||||
} catch (Exception ex) {
|
||||
LOG.warn("[MAILER] Smtp Configurations are missing : Reason {} ", ex.getMessage(), ex);
|
||||
}
|
||||
} else {
|
||||
DEFAULT_SMTP_SETTINGS = new SmtpSettings();
|
||||
}
|
||||
INSTANCE = new EmailUtil();
|
||||
INITIALIZED = true;
|
||||
} else {
|
||||
INITIALIZED = false;
|
||||
LOG.info("Email Util is already initialized");
|
||||
}
|
||||
}
|
||||
|
||||
private Mailer createMailer(SmtpSettings smtpServerSettings) {
|
||||
private static Mailer createMailer(SmtpSettings smtpServerSettings) {
|
||||
TransportStrategy strategy;
|
||||
switch (smtpServerSettings.getTransportationStrategy()) {
|
||||
case SMPTS:
|
||||
@ -100,10 +113,10 @@ public class EmailUtil {
|
||||
}
|
||||
|
||||
public void sendAccountStatus(User user, String action, String status) throws IOException, TemplateException {
|
||||
if (defaultSmtpSettings.getEnableSmtpServer()) {
|
||||
if (DEFAULT_SMTP_SETTINGS.getEnableSmtpServer()) {
|
||||
Map<String, String> templatePopulator = new HashMap<>();
|
||||
templatePopulator.put(ENTITY, EmailUtil.getInstance().getEmailingEntity());
|
||||
templatePopulator.put(SUPPORT_URL, EmailUtil.getInstance().getSupportUrl());
|
||||
templatePopulator.put(ENTITY, getEmailingEntity());
|
||||
templatePopulator.put(SUPPORT_URL, getSupportUrl());
|
||||
templatePopulator.put(USERNAME, user.getName());
|
||||
templatePopulator.put(ACTION_KEY, action);
|
||||
templatePopulator.put(ACTION_STATUS_KEY, status);
|
||||
@ -117,7 +130,7 @@ public class EmailUtil {
|
||||
}
|
||||
|
||||
public void sendEmailVerification(String emailVerificationLink, User user) throws IOException, TemplateException {
|
||||
if (defaultSmtpSettings.getEnableSmtpServer()) {
|
||||
if (DEFAULT_SMTP_SETTINGS.getEnableSmtpServer()) {
|
||||
Map<String, String> templatePopulator = new HashMap<>();
|
||||
templatePopulator.put(ENTITY, getEmailingEntity());
|
||||
templatePopulator.put(SUPPORT_URL, getSupportUrl());
|
||||
@ -135,7 +148,7 @@ public class EmailUtil {
|
||||
|
||||
public void sendPasswordResetLink(String passwordResetLink, User user, String subject, String templateFilePath)
|
||||
throws IOException, TemplateException {
|
||||
if (defaultSmtpSettings.getEnableSmtpServer()) {
|
||||
if (DEFAULT_SMTP_SETTINGS.getEnableSmtpServer()) {
|
||||
Map<String, String> templatePopulator = new HashMap<>();
|
||||
templatePopulator.put(ENTITY, getEmailingEntity());
|
||||
templatePopulator.put(SUPPORT_URL, getSupportUrl());
|
||||
@ -150,7 +163,7 @@ public class EmailUtil {
|
||||
public void sendTaskAssignmentNotificationToUser(
|
||||
String assigneeName, String email, String taskLink, Thread thread, String subject, String templateFilePath)
|
||||
throws IOException, TemplateException {
|
||||
if (defaultSmtpSettings.getEnableSmtpServer()) {
|
||||
if (DEFAULT_SMTP_SETTINGS.getEnableSmtpServer()) {
|
||||
Map<String, String> templatePopulator = new HashMap<>();
|
||||
templatePopulator.put("assignee", assigneeName);
|
||||
templatePopulator.put("createdBy", thread.getCreatedBy());
|
||||
@ -173,7 +186,7 @@ public class EmailUtil {
|
||||
String subject,
|
||||
String templateFilePath)
|
||||
throws IOException, TemplateException {
|
||||
if (defaultSmtpSettings.getEnableSmtpServer()) {
|
||||
if (DEFAULT_SMTP_SETTINGS.getEnableSmtpServer()) {
|
||||
Map<String, String> templatePopulator = new HashMap<>();
|
||||
templatePopulator.put("receiverName", email.split("@")[0]);
|
||||
templatePopulator.put("testResultName", testCaseName);
|
||||
@ -186,84 +199,17 @@ public class EmailUtil {
|
||||
}
|
||||
}
|
||||
|
||||
public Email buildEmailWithDefaultSender(EmailRequest request) {
|
||||
EmailPopulatingBuilder emailBuilder = EmailBuilder.startingBlank();
|
||||
if (request.getRecipientMails() != null
|
||||
&& request.getRecipientMails().size() != 0
|
||||
&& request.getSubject() != null
|
||||
&& !request.getSubject().equals("")) {
|
||||
// Sender Details
|
||||
emailBuilder.from(defaultSmtpSettings.getSenderMail());
|
||||
|
||||
// Recipient
|
||||
request
|
||||
.getRecipientMails()
|
||||
.forEach(
|
||||
(pair) -> {
|
||||
if (pair.getName() != null && !pair.getName().equals("")) {
|
||||
emailBuilder.to(pair.getName(), pair.getEmail());
|
||||
} else {
|
||||
emailBuilder.to(pair.getEmail());
|
||||
}
|
||||
});
|
||||
|
||||
// CC
|
||||
if (request.getCcMails() != null) {
|
||||
request
|
||||
.getCcMails()
|
||||
.forEach(
|
||||
(pair) -> {
|
||||
if (pair.getName() != null && !pair.getName().equals("")) {
|
||||
emailBuilder.cc(pair.getName(), pair.getEmail());
|
||||
} else {
|
||||
emailBuilder.cc(pair.getEmail());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// BCC
|
||||
if (request.getBccMails() != null) {
|
||||
request
|
||||
.getBccMails()
|
||||
.forEach(
|
||||
(pair) -> {
|
||||
if (pair.getName() != null && !pair.getName().equals("")) {
|
||||
emailBuilder.bcc(pair.getName(), pair.getEmail());
|
||||
} else {
|
||||
emailBuilder.bcc(pair.getEmail());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Subject
|
||||
if (request.getSubject() != null) {
|
||||
emailBuilder.withSubject(request.getSubject());
|
||||
}
|
||||
|
||||
if (request.getContent() != null) {
|
||||
if (request.getContentType() == EmailRequest.ContentType.HTML) {
|
||||
emailBuilder.withHTMLText(request.getContent());
|
||||
} else {
|
||||
emailBuilder.withPlainText(request.getContent());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new RuntimeException("Email Request is missing Required Details");
|
||||
}
|
||||
return emailBuilder.buildEmail();
|
||||
}
|
||||
|
||||
public void sendMail(
|
||||
String subject, Map<String, String> model, String to, String baseTemplatePackage, String templatePath)
|
||||
throws IOException, TemplateException {
|
||||
if (defaultSmtpSettings.getEnableSmtpServer()) {
|
||||
if (DEFAULT_SMTP_SETTINGS.getEnableSmtpServer()) {
|
||||
EmailPopulatingBuilder emailBuilder = EmailBuilder.startingBlank();
|
||||
emailBuilder.withSubject(subject);
|
||||
emailBuilder.to(to);
|
||||
emailBuilder.from(defaultSmtpSettings.getSenderMail());
|
||||
emailBuilder.from(DEFAULT_SMTP_SETTINGS.getSenderMail());
|
||||
|
||||
templateConfiguration.setClassForTemplateLoading(getClass(), baseTemplatePackage);
|
||||
Template template = templateConfiguration.getTemplate(templatePath);
|
||||
TEMPLATE_CONFIGURATION.setClassForTemplateLoading(getClass(), baseTemplatePackage);
|
||||
Template template = TEMPLATE_CONFIGURATION.getTemplate(templatePath);
|
||||
|
||||
// write the freemarker output to a StringWriter
|
||||
StringWriter stringWriter = new StringWriter();
|
||||
@ -275,17 +221,17 @@ public class EmailUtil {
|
||||
}
|
||||
|
||||
public void sendMail(Email email) {
|
||||
if (mailer != null && defaultSmtpSettings.getEnableSmtpServer()) {
|
||||
mailer.sendMail(email, true);
|
||||
if (MAILER != null && DEFAULT_SMTP_SETTINGS.getEnableSmtpServer()) {
|
||||
MAILER.sendMail(email, true);
|
||||
}
|
||||
}
|
||||
|
||||
public String buildBaseUrl(URI uri) {
|
||||
try {
|
||||
if (CommonUtil.nullOrEmpty(this.defaultSmtpSettings.getOpenMetadataUrl())) {
|
||||
if (CommonUtil.nullOrEmpty(DEFAULT_SMTP_SETTINGS.getOpenMetadataUrl())) {
|
||||
return String.format("%s://%s", uri.getScheme(), uri.getHost());
|
||||
} else {
|
||||
URI serverUrl = new URI(this.defaultSmtpSettings.getOpenMetadataUrl());
|
||||
URI serverUrl = new URI(DEFAULT_SMTP_SETTINGS.getOpenMetadataUrl());
|
||||
return String.format("%s://%s", serverUrl.getScheme(), serverUrl.getHost());
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
@ -293,53 +239,65 @@ public class EmailUtil {
|
||||
}
|
||||
}
|
||||
|
||||
public void testConnection() {
|
||||
mailer.testConnection();
|
||||
}
|
||||
|
||||
public static class EmailUtilBuilder {
|
||||
private EmailUtilBuilder() {}
|
||||
|
||||
public static void build(SmtpSettings smtpServerSettings) {
|
||||
if (INSTANCE == null) {
|
||||
INSTANCE = new EmailUtil(smtpServerSettings);
|
||||
public static void sendInviteMailToAdmin(User user, String pwd) {
|
||||
if (DEFAULT_SMTP_SETTINGS.getEnableSmtpServer()) {
|
||||
Map<String, String> templatePopulator = new HashMap<>();
|
||||
templatePopulator.put(EmailUtil.ENTITY, EmailUtil.getInstance().getEmailingEntity());
|
||||
templatePopulator.put(EmailUtil.SUPPORT_URL, EmailUtil.getInstance().getSupportUrl());
|
||||
templatePopulator.put(EmailUtil.USERNAME, user.getName());
|
||||
templatePopulator.put(EmailUtil.PASSWORD, pwd);
|
||||
templatePopulator.put(EmailUtil.APPLICATION_LOGIN_LINK, EmailUtil.getInstance().getOMUrl());
|
||||
try {
|
||||
EmailUtil.getInstance()
|
||||
.sendMail(
|
||||
EmailUtil.getInstance().getEmailInviteSubject(),
|
||||
templatePopulator,
|
||||
user.getEmail(),
|
||||
EmailUtil.EMAIL_TEMPLATE_BASEPATH,
|
||||
EmailUtil.INVITE_RANDOM_PWD);
|
||||
} catch (Exception ex) {
|
||||
LOG.error("Failed in sending Mail to user [{}]. Reason : {}", user.getEmail(), ex.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getEmailVerificationSubject() {
|
||||
return String.format(EMAIL_VERIFICATION_SUBJECT, defaultSmtpSettings.getEmailingEntity());
|
||||
public void testConnection() {
|
||||
MAILER.testConnection();
|
||||
}
|
||||
|
||||
private String getEmailVerificationSubject() {
|
||||
return String.format(EMAIL_VERIFICATION_SUBJECT, DEFAULT_SMTP_SETTINGS.getEmailingEntity());
|
||||
}
|
||||
|
||||
public String getPasswordResetSubject() {
|
||||
return String.format(PASSWORD_RESET_SUBJECT, defaultSmtpSettings.getEmailingEntity());
|
||||
return String.format(PASSWORD_RESET_SUBJECT, DEFAULT_SMTP_SETTINGS.getEmailingEntity());
|
||||
}
|
||||
|
||||
public String getAccountStatusChangeSubject() {
|
||||
return String.format(ACCOUNT_STATUS_SUBJECT, defaultSmtpSettings.getEmailingEntity());
|
||||
private String getAccountStatusChangeSubject() {
|
||||
return String.format(ACCOUNT_STATUS_SUBJECT, DEFAULT_SMTP_SETTINGS.getEmailingEntity());
|
||||
}
|
||||
|
||||
public String getEmailInviteSubject() {
|
||||
return String.format(INVITE_SUBJECT, defaultSmtpSettings.getEmailingEntity());
|
||||
return String.format(INVITE_SUBJECT, DEFAULT_SMTP_SETTINGS.getEmailingEntity());
|
||||
}
|
||||
|
||||
public String getTaskAssignmentSubject() {
|
||||
return String.format(TASK_SUBJECT, defaultSmtpSettings.getEmailingEntity());
|
||||
return String.format(TASK_SUBJECT, DEFAULT_SMTP_SETTINGS.getEmailingEntity());
|
||||
}
|
||||
|
||||
public String getTestResultSubject() {
|
||||
return String.format(TEST_SUBJECT, defaultSmtpSettings.getEmailingEntity());
|
||||
return String.format(TEST_SUBJECT, DEFAULT_SMTP_SETTINGS.getEmailingEntity());
|
||||
}
|
||||
|
||||
public String getEmailingEntity() {
|
||||
return defaultSmtpSettings.getEmailingEntity();
|
||||
return DEFAULT_SMTP_SETTINGS.getEmailingEntity();
|
||||
}
|
||||
|
||||
public String getSupportUrl() {
|
||||
return defaultSmtpSettings.getSupportUrl();
|
||||
return DEFAULT_SMTP_SETTINGS.getSupportUrl();
|
||||
}
|
||||
|
||||
public String getOMUrl() {
|
||||
return defaultSmtpSettings.getOpenMetadataUrl();
|
||||
return DEFAULT_SMTP_SETTINGS.getOpenMetadataUrl();
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,223 @@
|
||||
package org.openmetadata.service.util;
|
||||
|
||||
import static org.openmetadata.common.utils.CommonUtil.nullOrEmpty;
|
||||
import static org.openmetadata.schema.auth.SSOAuthMechanism.SsoServiceType.AUTH_0;
|
||||
import static org.openmetadata.schema.auth.SSOAuthMechanism.SsoServiceType.AZURE;
|
||||
import static org.openmetadata.schema.auth.SSOAuthMechanism.SsoServiceType.CUSTOM_OIDC;
|
||||
import static org.openmetadata.schema.auth.SSOAuthMechanism.SsoServiceType.GOOGLE;
|
||||
import static org.openmetadata.schema.auth.SSOAuthMechanism.SsoServiceType.OKTA;
|
||||
import static org.openmetadata.schema.entity.teams.AuthenticationMechanism.AuthType.JWT;
|
||||
import static org.openmetadata.schema.entity.teams.AuthenticationMechanism.AuthType.SSO;
|
||||
import static org.openmetadata.service.Entity.ADMIN_USER_NAME;
|
||||
import static org.openmetadata.service.resources.teams.UserResource.USER_PROTECTED_FIELDS;
|
||||
|
||||
import at.favre.lib.crypto.bcrypt.BCrypt;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.openmetadata.schema.api.configuration.airflow.AirflowConfiguration;
|
||||
import org.openmetadata.schema.api.security.AuthenticationConfiguration;
|
||||
import org.openmetadata.schema.auth.BasicAuthMechanism;
|
||||
import org.openmetadata.schema.auth.JWTAuthMechanism;
|
||||
import org.openmetadata.schema.auth.JWTTokenExpiry;
|
||||
import org.openmetadata.schema.auth.SSOAuthMechanism;
|
||||
import org.openmetadata.schema.entity.teams.AuthenticationMechanism;
|
||||
import org.openmetadata.schema.entity.teams.User;
|
||||
import org.openmetadata.schema.security.client.OpenMetadataJWTClientConfig;
|
||||
import org.openmetadata.service.Entity;
|
||||
import org.openmetadata.service.OpenMetadataApplicationConfig;
|
||||
import org.openmetadata.service.exception.EntityNotFoundException;
|
||||
import org.openmetadata.service.jdbi3.EntityRepository;
|
||||
import org.openmetadata.service.jdbi3.UserRepository;
|
||||
import org.openmetadata.service.security.jwt.JWTTokenGenerator;
|
||||
|
||||
@Slf4j
|
||||
public final class UserUtil {
|
||||
private static final String COLON_DELIMITER = ":";
|
||||
|
||||
public static void handleBasicAuth(Set<String> adminUsers, String domain) {
|
||||
try {
|
||||
for (String adminUser : adminUsers) {
|
||||
if (adminUser.contains(COLON_DELIMITER)) {
|
||||
String[] tokens = adminUser.split(COLON_DELIMITER);
|
||||
addUserForBasicAuth(tokens[0], tokens[1], domain);
|
||||
} else {
|
||||
boolean isDefaultAdmin = adminUser.equals(ADMIN_USER_NAME);
|
||||
String token = PasswordUtil.generateRandomPassword();
|
||||
if (isDefaultAdmin) {
|
||||
token = ADMIN_USER_NAME;
|
||||
}
|
||||
addUserForBasicAuth(adminUser, token, domain);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.error("Failed in Basic Auth Setup. Reason : {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public static void addUserForBasicAuth(String username, String pwd, String domain) throws IOException {
|
||||
EntityRepository<User> userRepository = Entity.getEntityRepository(Entity.USER);
|
||||
User originalUser;
|
||||
try {
|
||||
List<String> fields = userRepository.getAllowedFieldsCopy();
|
||||
fields.add(USER_PROTECTED_FIELDS);
|
||||
originalUser = userRepository.getByName(null, username, new EntityUtil.Fields(fields, String.join(",", fields)));
|
||||
if (originalUser.getAuthenticationMechanism() == null) {
|
||||
updateUserWithHashedPwd(originalUser, pwd);
|
||||
}
|
||||
addOrUpdateUser(originalUser);
|
||||
} catch (EntityNotFoundException e) {
|
||||
// TODO: Not the best way ! :(
|
||||
User user = user(username, domain, username).withIsAdmin(true).withIsEmailVerified(true);
|
||||
updateUserWithHashedPwd(user, pwd);
|
||||
addOrUpdateUser(user);
|
||||
EmailUtil.sendInviteMailToAdmin(user, pwd);
|
||||
}
|
||||
}
|
||||
|
||||
public static void updateUserWithHashedPwd(User user, String pwd) {
|
||||
String hashedPwd = BCrypt.withDefaults().hashToString(12, pwd.toCharArray());
|
||||
user.setAuthenticationMechanism(
|
||||
new AuthenticationMechanism()
|
||||
.withAuthType(AuthenticationMechanism.AuthType.BASIC)
|
||||
.withConfig(new BasicAuthMechanism().withPassword(hashedPwd)));
|
||||
}
|
||||
|
||||
public static void addUsers(Set<String> users, String domain, Boolean isAdmin) {
|
||||
for (String userName : users) {
|
||||
User user = user(userName, domain, userName).withIsAdmin(isAdmin);
|
||||
addOrUpdateUser(user);
|
||||
}
|
||||
}
|
||||
|
||||
public static User addOrUpdateUser(User user) {
|
||||
EntityRepository<User> userRepository = Entity.getEntityRepository(Entity.USER);
|
||||
try {
|
||||
RestUtil.PutResponse<User> addedUser = userRepository.createOrUpdate(null, user);
|
||||
// should not log the user auth details in LOGS
|
||||
LOG.debug("Added user entry: {}", addedUser.getEntity().getName());
|
||||
return addedUser.getEntity();
|
||||
} catch (Exception exception) {
|
||||
// In HA set up the other server may have already added the user.
|
||||
LOG.debug("Caught exception", exception);
|
||||
user.setAuthenticationMechanism(null);
|
||||
LOG.debug("User entry: {} already exists.", user.getName());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static User user(String name, String domain, String updatedBy) {
|
||||
return new User()
|
||||
.withId(UUID.randomUUID())
|
||||
.withName(name)
|
||||
.withFullyQualifiedName(name)
|
||||
.withEmail(name + "@" + domain)
|
||||
.withUpdatedBy(updatedBy)
|
||||
.withUpdatedAt(System.currentTimeMillis())
|
||||
.withIsBot(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method add auth mechanism in the following way:
|
||||
*
|
||||
* <ul>
|
||||
* <li>If original user has already an authMechanism, add it to the user
|
||||
* <li>Otherwise:
|
||||
* <ul>
|
||||
* <li>If airflow configuration is 'openmetadata' and server auth provider is not basic, add JWT auth
|
||||
* mechanism from Airflow configuration
|
||||
* <li>Otherwise:
|
||||
* <ul>
|
||||
* <li>If airflow configuration is 'basic', add JWT auth mechanism with a generated token which does not
|
||||
* expire
|
||||
* <li>Otherwise, add SSO auth mechanism from Airflow configuration
|
||||
* </ul>
|
||||
* </ul>
|
||||
* </ul>
|
||||
*/
|
||||
public static User addOrUpdateBotUser(User user, OpenMetadataApplicationConfig openMetadataApplicationConfig) {
|
||||
User originalUser = retrieveWithAuthMechanism(user);
|
||||
// the user did not have an auth mechanism
|
||||
AuthenticationMechanism authMechanism = originalUser != null ? originalUser.getAuthenticationMechanism() : null;
|
||||
if (authMechanism == null) {
|
||||
AuthenticationConfiguration authConfig = openMetadataApplicationConfig.getAuthenticationConfiguration();
|
||||
AirflowConfiguration airflowConfig = openMetadataApplicationConfig.getAirflowConfiguration();
|
||||
// if the auth provider is "openmetadata" in the configuration set JWT as auth mechanism
|
||||
if ("openmetadata".equals(airflowConfig.getAuthProvider()) && !"basic".equals(authConfig.getProvider())) {
|
||||
OpenMetadataJWTClientConfig jwtClientConfig = airflowConfig.getAuthConfig().getOpenmetadata();
|
||||
authMechanism = buildAuthMechanism(JWT, buildJWTAuthMechanism(jwtClientConfig, user));
|
||||
} else {
|
||||
// Otherwise, set auth mechanism from airflow configuration
|
||||
// TODO: https://github.com/open-metadata/OpenMetadata/issues/7712
|
||||
if (airflowConfig.getAuthConfig() != null && !"basic".equals(authConfig.getProvider())) {
|
||||
switch (authConfig.getProvider()) {
|
||||
case "no-auth":
|
||||
break;
|
||||
case "azure":
|
||||
authMechanism =
|
||||
buildAuthMechanism(SSO, buildAuthMechanismConfig(AZURE, airflowConfig.getAuthConfig().getAzure()));
|
||||
break;
|
||||
case "google":
|
||||
authMechanism =
|
||||
buildAuthMechanism(SSO, buildAuthMechanismConfig(GOOGLE, airflowConfig.getAuthConfig().getGoogle()));
|
||||
break;
|
||||
case "okta":
|
||||
authMechanism =
|
||||
buildAuthMechanism(SSO, buildAuthMechanismConfig(OKTA, airflowConfig.getAuthConfig().getOkta()));
|
||||
break;
|
||||
case "auth0":
|
||||
authMechanism =
|
||||
buildAuthMechanism(SSO, buildAuthMechanismConfig(AUTH_0, airflowConfig.getAuthConfig().getAuth0()));
|
||||
break;
|
||||
case "custom-oidc":
|
||||
authMechanism =
|
||||
buildAuthMechanism(
|
||||
SSO, buildAuthMechanismConfig(CUSTOM_OIDC, airflowConfig.getAuthConfig().getCustomOidc()));
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException(
|
||||
String.format(
|
||||
"Unexpected auth provider [%s] for bot [%s]", authConfig.getProvider(), user.getName()));
|
||||
}
|
||||
} else if ("basic".equals(authConfig.getProvider())) {
|
||||
authMechanism = buildAuthMechanism(JWT, buildJWTAuthMechanism(null, user));
|
||||
}
|
||||
}
|
||||
}
|
||||
user.setAuthenticationMechanism(authMechanism);
|
||||
user.setDescription(user.getDescription());
|
||||
user.setDisplayName(user.getDisplayName());
|
||||
user.setUpdatedBy(ADMIN_USER_NAME);
|
||||
return UserUtil.addOrUpdateUser(user);
|
||||
}
|
||||
|
||||
private static JWTAuthMechanism buildJWTAuthMechanism(OpenMetadataJWTClientConfig jwtClientConfig, User user) {
|
||||
return Objects.isNull(jwtClientConfig) || nullOrEmpty(jwtClientConfig.getJwtToken())
|
||||
? JWTTokenGenerator.getInstance().generateJWTToken(user, JWTTokenExpiry.Unlimited)
|
||||
: new JWTAuthMechanism()
|
||||
.withJWTToken(jwtClientConfig.getJwtToken())
|
||||
.withJWTTokenExpiry(JWTTokenExpiry.Unlimited);
|
||||
}
|
||||
|
||||
private static SSOAuthMechanism buildAuthMechanismConfig(
|
||||
SSOAuthMechanism.SsoServiceType ssoServiceType, Object config) {
|
||||
return new SSOAuthMechanism().withSsoServiceType(ssoServiceType).withAuthConfig(config);
|
||||
}
|
||||
|
||||
private static AuthenticationMechanism buildAuthMechanism(AuthenticationMechanism.AuthType authType, Object config) {
|
||||
return new AuthenticationMechanism().withAuthType(authType).withConfig(config);
|
||||
}
|
||||
|
||||
private static User retrieveWithAuthMechanism(User user) {
|
||||
EntityRepository<User> userRepository = UserRepository.class.cast(Entity.getEntityRepository(Entity.USER));
|
||||
try {
|
||||
return userRepository.getByName(null, user.getName(), new EntityUtil.Fields(List.of("authenticationMechanism")));
|
||||
} catch (IOException | EntityNotFoundException e) {
|
||||
LOG.debug("Bot entity: {} does not exists.", user);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user