All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.imixs.marty.security.UserGroupService Maven / Gradle / Ivy

/*******************************************************************************
 *  Imixs Workflow Technology
 *  Copyright (C) 2001, 2008 Imixs Software Solutions GmbH,  
 *  http://www.imixs.com
 *  
 *  This program is free software; you can redistribute it and/or 
 *  modify it under the terms of the GNU General Public License 
 *  as published by the Free Software Foundation; either version 2 
 *  of the License, or (at your option) any later version.
 *  
 *  This program is distributed in the hope that it will be useful, 
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of 
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 
 *  General Public License for more details.
 *  
 *  You can receive a copy of the GNU General Public
 *  License at http://www.gnu.org/licenses/gpl.html
 *  
 *  Contributors:  
 *  	Imixs Software Solutions GmbH - initial API and implementation
 *  	Ralph Soika
 *******************************************************************************/
package org.imixs.marty.security;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import jakarta.annotation.Resource;
import jakarta.annotation.security.DeclareRoles;
import jakarta.annotation.security.RunAs;
import jakarta.ejb.EJB;
import jakarta.ejb.LocalBean;
import jakarta.ejb.SessionContext;
import jakarta.ejb.Stateless;
import jakarta.inject.Inject;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.Query;

import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.imixs.workflow.ItemCollection;
import org.imixs.workflow.WorkflowKernel;
import org.imixs.workflow.engine.DocumentService;
import org.imixs.workflow.exceptions.AccessDeniedException;

/**
 * user Group service to provide method for managing user and groups settings.
 * 
 * @author rsoika
 * 
 */
@DeclareRoles({ "org.imixs.ACCESSLEVEL.MANAGERACCESS" })
@Stateless
@RunAs("org.imixs.ACCESSLEVEL.MANAGERACCESS")
@LocalBean
public class UserGroupService {

    public static String ACCESSLEVEL_NOACCESS = "org.imixs.ACCESSLEVEL.NOACCESS";

    public static String DEFAULT_ACCOUNT = "admin";
    public static String DEFAULT_PASSWORD = "adminadmin";

    public static final String[] CORE_GROUPS = { "org.imixs.ACCESSLEVEL.MANAGERACCESS",
            "org.imixs.ACCESSLEVEL.EDITORACCESS", "org.imixs.ACCESSLEVEL.AUTHORACCESS",
            "org.imixs.ACCESSLEVEL.READERACCESS" };
    public static final String[] DEPRECATED_CORE_GROUPS = { "IMIXS-WORKFLOW-Manager", "IMIXS-WORKFLOW-Editor",
            "IMIXS-WORKFLOW-Author", "IMIXS-WORKFLOW-Reader" };

    @PersistenceContext(unitName = "org.imixs.workflow.jpa")
    private EntityManager manager;

    @Resource
    SessionContext ctx;

    @EJB
    DocumentService documentService;

    @Inject
    @ConfigProperty(name = "security.userid.input.mode", defaultValue = "LOWERCASE")
    String userInputMode;

    private static Logger logger = Logger.getLogger(UserGroupService.class.getName());

    /**
     * This method verifies the profile data and creates or update the corresponding
     * user entries in the user tables.
     * 

* NOTE: this method did not change a userid. To do this use the method * changeUser! *

* The Method also verifies deprecated role names, fix it and prints out a * warning in such a case. *

* If a new userId entity is generated but no password is provided, the method * generates an encrypted random password * * @param profile */ @SuppressWarnings("unchecked") public void updateUser(ItemCollection profile) { boolean debug = logger.isLoggable(Level.FINE); String sType = profile.getItemValueString("Type"); if (!("profile".equals(sType))) return; String sID = profile.getItemValueString("txtName"); String sPassword = profile.getItemValueString("txtPassword"); Collection groups = profile.getItemValue("txtGroups"); UserId user = null; user = manager.find(UserId.class, sID); if (user == null) { user = new UserId(sID); manager.persist(user); } // encrypt and update password if (sPassword != null && !"".equals(sPassword)) { String sEncryptedPasswort = Crypter.crypt(sPassword); user.setPassword(sEncryptedPasswort); // remove password.... profile.removeItem("txtPassword"); profile.removeItem("txtpassword2"); logger.info("password change for userid '" + sID + "' by '" + ctx.getCallerPrincipal().getName() + "'"); profile.replaceItemValue("txtpasswordhash", sEncryptedPasswort); } // if user object does no have a password set then we genreate a random password // (issue 373) if (user.getPassword() == null || user.getPassword().isEmpty()) { user.setPassword(Crypter.crypt(WorkflowKernel.generateUniqueID())); } // find group relation ships Set groupList = new HashSet(); for (String aGroup : groups) { // we do not except empty groupnames here! if (aGroup != null && !aGroup.isEmpty()) { UserGroup group = manager.find(UserGroup.class, aGroup); // if group dos not exist - create it... if (group == null) { group = new UserGroup(aGroup); manager.persist(group); } groupList.add(group); } } // if grouplist is empty we set the role 'org.imixs.ACCESSLEVEL.NOACCESS' if (groupList.size() == 0) { // verify if no access exists... UserGroup noAccessGroup = manager.find(UserGroup.class, ACCESSLEVEL_NOACCESS); // if group dos not exist - create it... if (noAccessGroup == null) { noAccessGroup = new UserGroup(ACCESSLEVEL_NOACCESS); manager.persist(noAccessGroup); } groupList.add(noAccessGroup); } // update groups user.setUserGroups(groupList); // log debug messages if (debug) { logger.fine("...update '" + sID + "' Groups: "); groups.forEach(n -> logger.fine("... '" + n + "'")); } } /** * This method changes the userID of an existing user entry and updates the * userGroup table entries. * * @param oldID - the existing userEntry * @param newID - the name of the new id */ public void changeUserId(String oldID, String newID) { UserId user = null; // test if new userid still exits user = manager.find(UserId.class, newID); if (user != null) { logger.warning("changeUser - new userId '" + newID + "'is still in Use!"); return; } // find old user entry.... user = manager.find(UserId.class, oldID); if (user == null) { logger.warning("changeUser - UserID '" + oldID + "' not found!"); return; } // change id UserId newUser = new UserId(newID); newUser.setPassword(user.getPassword()); newUser.setUserGroups(user.getUserGroups()); manager.persist(newUser); // remove old manager.remove(user); // log logger.info("changeUserId '" + oldID + "' to '" + newID + "' by '" + ctx.getCallerPrincipal().getName()); } /** * This method deletes the userID of an existing user entry and also the * userGroup table entries. * * @param userID - the existing userEntry */ public void removeUserId(String userID) { UserId user = null; // test if userid exits user = manager.find(UserId.class, userID); if (user == null) { logger.warning("removeUserId - userId '" + userID + "' did not exist!"); return; } // remove old manager.remove(user); // log logger.info("removeUserId '" + userID + "' by '" + ctx.getCallerPrincipal().getName()); } /** * This method verifies if a default user id already exists. If no userID exists * the method generates a default account 'admin' with password 'adminadmin' * * @throws AccessDeniedException */ @SuppressWarnings("unchecked") public void initUserIDs() { logger.finest("......init UserIDs..."); // verfiy existing profiles verifyExistingProfileData(); // migrate deprecated user roles // Issue #373 migrateDeprecatedUserRoles(); // create default admin account if missing String sAdminAccount = DEFAULT_ACCOUNT; if ("uppercase".equalsIgnoreCase(userInputMode)) { sAdminAccount = sAdminAccount.toUpperCase(); } String sQuery = "SELECT user FROM UserId AS user WHERE user.id='" + sAdminAccount + "'"; Query q = manager.createQuery(sQuery); q.setFirstResult(0); q.setMaxResults(1); Collection entityList = q.getResultList(); if (entityList == null || entityList.size() == 0) { logger.info("Create default admin account..."); // create a default account ItemCollection profile = new ItemCollection(); profile.replaceItemValue("type", "profile"); profile.replaceItemValue("txtName", sAdminAccount); // set default password profile.replaceItemValue("txtPassword", DEFAULT_PASSWORD); profile.replaceItemValue("$WorkflowGroup", "Profile"); profile.replaceItemValue("txtGroups", "IMIXS-WORKFLOW-Manager"); // deprecated profile.appendItemValue("txtGroups", "org.imixs.ACCESSLEVEL.MANAGERACCESS"); // hard coded version nummer! profile.replaceItemValue("$modelversion", "system-de-0.0.1"); profile.replaceItemValue("$workflowgroup", "Profil"); profile.replaceItemValue("$processid", 210); try { updateUser(profile); documentService.save(profile); } catch (AccessDeniedException e) { logger.warning("UserGroupService - unable to initialize default admin account"); logger.severe(e.getMessage()); // throw new RuntimeException(e); return; } } } /** * This method migrates deprecated user roles *

*

    *
  • IMIXS-WORKFLOW-Manager => org.imixs.ACCESSLEVEL.MANAGERACCESS *
  • IMIXS-WORKFLOW-Editor => org.imixs.ACCESSLEVEL.EDITORACCESS *
  • IMIXS-WORKFLOW-Author => org.imixs.ACCESSLEVEL.AUTHORACCESS *
  • IMIXS-WORKFLOW-Reader => org.imixs.ACCESSLEVEL.READERACCESS *

    * First the method tests if a migration is necessary. For that the method tests * if the new roles already are existing in the system. Only if not the * migration is started *

    * This method can be removed in later versions (but it may need some time) */ public void migrateDeprecatedUserRoles() { boolean needMigration = false; // test existence of roles for (String aGroup : CORE_GROUPS) { UserGroup group = manager.find(UserGroup.class, aGroup); // if group dos not exist migration is necessary... if (group == null) { needMigration = true; // don't waste time break; } } if (!needMigration) { // no migration needed logger.info("...Imixs core userGroups OK..."); return; } // start migration logger.info("*************************************************"); logger.info("...System contains deprecated userGroups!"); logger.info("...migration to new Imixs core user groups starting...."); logger.info("*************************************************"); // first create missing core user groups for (String aGroup : CORE_GROUPS) { UserGroup group = manager.find(UserGroup.class, aGroup); if (group == null) { group = new UserGroup(aGroup); manager.persist(group); } } // migrate existing user profiles and userGroup objects... migrateExistingProfileData(); logger.info("*************************************************"); logger.info("...migration to new Imixs core user groups finished successful."); logger.info("*************************************************"); } /** * This method is called by the initUserIDs method. *

    * The method tries to update the userData based on existing profiles data and * restore groups and encrypted passwords. * */ @SuppressWarnings("unchecked") private void verifyExistingProfileData() { logger.info("...verify existing profile data..."); List profiles = documentService.getDocumentsByType("profile"); for (ItemCollection profile : profiles) { String id = profile.getItemValueString("txtname"); // try to update data..... UserId user = null; user = manager.find(UserId.class, id); if (user != null) { // user object exits continue; } // userobject does not exist, so we restore the user object based on the // existing profile data if the profile contains a password hash.... String passwordhash = profile.getItemValueString("txtpasswordhash"); if (!passwordhash.isEmpty()) { logger.info("...restore userid '" + id + "' from existing profile data..."); user = new UserId(id); user.setPassword(passwordhash); manager.persist(user); // find group relation ships Collection groups = profile.getItemValue("txtGroups"); Set groupList = new HashSet(); for (String aGroup : groups) { // we do not except empty groupnames here! if (aGroup != null && !aGroup.isEmpty()) { UserGroup group = manager.find(UserGroup.class, aGroup); // if group dos not exist - create it... if (group == null) { group = new UserGroup(aGroup); manager.persist(group); } groupList.add(group); } } // if grouplist is empty we set the role 'org.imixs.ACCESSLEVEL.NOACCESS' if (groupList.size() == 0) { // verify if no access exists... UserGroup noAccessGroup = manager.find(UserGroup.class, ACCESSLEVEL_NOACCESS); // if group dos not exist - create it... if (noAccessGroup == null) { noAccessGroup = new UserGroup(ACCESSLEVEL_NOACCESS); manager.persist(noAccessGroup); } groupList.add(noAccessGroup); } // update groups user.setUserGroups(groupList); } } } /** * This method is called by the initUserIDs method. *

    * The method looks for deprecated roles in the existing profiles data and * migrates groups if necessary. * */ @SuppressWarnings("unchecked") private void migrateExistingProfileData() { int count = 0; List deprecatedCoreGrouplist = Arrays.asList(DEPRECATED_CORE_GROUPS); logger.info("migrate deprecated profile data..."); List profiles = documentService.getDocumentsByType("profile"); for (ItemCollection profile : profiles) { String id = profile.getItemValueString("txtname"); UserId user = null; user = manager.find(UserId.class, id); if (user != null) { logger.info("...migate deprecated userroles for '" + id + "' ..."); // find group relation ships List groupNames = profile.getItemValue("txtGroups"); // check if we have deprecated roles List newGroupNames = new ArrayList(); newGroupNames.addAll(groupNames); for (String aGroup : groupNames) { if (deprecatedCoreGrouplist.contains(aGroup) && !groupNames.contains(getCoreGroupName(aGroup))) { String newGroup = getCoreGroupName(aGroup); logger.info("..." + id + " contains depreacted userrole " + aGroup); logger.info("... Group will be automatically migrated to " + newGroup); newGroupNames.add(newGroup); } } // update group object in any case! logger.info("...Updating UserGroup objects...."); Set groupList = new HashSet(); for (String aGroup : newGroupNames) { // we do not except empty groupnames here! if (aGroup != null && !aGroup.isEmpty()) { UserGroup group = manager.find(UserGroup.class, aGroup); // if group dos not exist - create it... if (group == null) { group = new UserGroup(aGroup); manager.persist(group); } groupList.add(group); } } user.setUserGroups(groupList); // if the user object contains an empty password we set a new encrypted random // password! if (user.getPassword() == null || user.getPassword().isEmpty()) { // #issue 373 logger.info("..." + id + " contains empty password - set random password..."); user.setPassword(Crypter.crypt(WorkflowKernel.generateUniqueID())); } // update also profile? if (newGroupNames.size() != groupNames.size()) { profile.setItemValue("txtGroups", newGroupNames); documentService.save(profile); count++; } } } logger.info("... " + count + " user profiles updated...."); } /** * Returns the deprecated group name for a given core group name *

      *
    • IMIXS-WORKFLOW-Manager => org.imixs.ACCESSLEVEL.MANAGERACCESS *
    • IMIXS-WORKFLOW-Editor => org.imixs.ACCESSLEVEL.EDITORACCESS *
    • IMIXS-WORKFLOW-Author => org.imixs.ACCESSLEVEL.AUTHORACCESS *
    • IMIXS-WORKFLOW-Reader => org.imixs.ACCESSLEVEL.READERACCESS *

      * * @param newGroupName * @return */ public String getDeprecatedGroupName(String newGroupName) { List grouplist = Arrays.asList(CORE_GROUPS); int pos = grouplist.indexOf(newGroupName); if (pos >= 0) { return DEPRECATED_CORE_GROUPS[pos]; } return null; } /** * Returns the core group name for a given deprecated group name * * @param deprecatedGroupName * @return */ public String getCoreGroupName(String deprecatedGroupName) { List grouplist = Arrays.asList(DEPRECATED_CORE_GROUPS); int pos = grouplist.indexOf(deprecatedGroupName); if (pos >= 0) { return CORE_GROUPS[pos]; } return null; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy