com.sonicle.webtop.core.app.shiro.WTRealm.java Source code

Java tutorial

Introduction

Here is the source code for com.sonicle.webtop.core.app.shiro.WTRealm.java

Source

/*
 * Copyright (C) 2019 Sonicle S.r.l.
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License version 3 as published by
 * the Free Software Foundation with the addition of the following permission
 * added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED
 * WORK IN WHICH THE COPYRIGHT IS OWNED BY SONICLE, SONICLE DISCLAIMS THE
 * WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
 *
 * 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 Affero General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program; if not, see http://www.gnu.org/licenses or write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301 USA.
 *
 * You can contact Sonicle S.r.l. at email address sonicle[at]sonicle[dot]com
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU Affero General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU Affero General Public License
 * version 3, these Appropriate Legal Notices must retain the display of the
 * Sonicle logo and Sonicle copyright notice. If the display of the logo is not
 * reasonably feasible for technical reasons, the Appropriate Legal Notices must
 * display the words "Copyright (C) 2019 Sonicle S.r.l.".
 */
package com.sonicle.webtop.core.app.shiro;

import com.sonicle.security.Principal;
import com.sonicle.security.AuthenticationDomain;
import com.sonicle.security.auth.DirectoryException;
import com.sonicle.security.auth.DirectoryManager;
import com.sonicle.security.auth.directory.AbstractDirectory;
import com.sonicle.security.auth.directory.AbstractDirectory.AuthUser;
import com.sonicle.security.auth.directory.DirectoryOptions;
import com.sonicle.webtop.core.app.CoreManifest;
import com.sonicle.webtop.core.app.WebTopManager;
import com.sonicle.webtop.core.app.WebTopApp;
import com.sonicle.webtop.core.app.sdk.WTMultiCauseWarnException;
import com.sonicle.webtop.core.bol.ODomain;
import com.sonicle.webtop.core.bol.ORolePermission;
import com.sonicle.webtop.core.bol.OUser;
import com.sonicle.webtop.core.model.ServicePermission;
import com.sonicle.webtop.core.bol.model.RoleWithSource;
import com.sonicle.webtop.core.bol.model.UserEntity;
import com.sonicle.webtop.core.sdk.UserProfileId;
import com.sonicle.webtop.core.sdk.WTException;
import java.net.URISyntaxException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *
 * @author malbinola
 */
public class WTRealm extends AuthorizingRealm {
    public static final String NAME = "wtrealm";
    private final static Logger logger = (Logger) LoggerFactory.getLogger(WTRealm.class);
    private final Object lock1 = new Object();

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        if (token instanceof UsernamePasswordToken) {
            UsernamePasswordToken upt = (UsernamePasswordToken) token;

            String domainId = null;
            if (token instanceof UsernamePasswordDomainToken) {
                domainId = ((UsernamePasswordDomainToken) token).getDomain();
            }
            //logger.debug("isRememberMe={}",upt.isRememberMe());

            String sprincipal = (String) upt.getPrincipal();
            String internetDomain = StringUtils.lowerCase(StringUtils.substringAfterLast(sprincipal, "@"));
            String username = StringUtils.substringBeforeLast(sprincipal, "@");
            logger.trace("doGetAuthenticationInfo [{}, {}, {}]", domainId, internetDomain, username);

            Principal principal = authenticateUser(domainId, internetDomain, username, upt.getPassword());

            // Update token with new values resulting from authentication
            if (token instanceof UsernamePasswordDomainToken) {
                ((UsernamePasswordDomainToken) token).setDomain(principal.getDomainId());
            }
            upt.setUsername(principal.getUserId());

            return new WTAuthenticationInfo(principal, upt.getPassword(), this.getName());

        } else {
            return null;
        }
    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        if (principals == null)
            throw new AuthorizationException("PrincipalCollection method argument cannot be null.");

        try {
            Principal principal = (Principal) principals.getPrimaryPrincipal();
            logger.trace("doGetAuthorizationInfo [{}]", principal);
            return loadAuthorizationInfo(principal);

        } catch (Exception ex) {
            throw new AuthorizationException(ex);
        }
    }

    /*
     * Protect caching of impersonated authorization from original user authorization cache
     */
    @Override
    protected Object getAuthorizationCacheKey(PrincipalCollection principals) {
        if (principals.getPrimaryPrincipal() instanceof com.sonicle.security.Principal) {
            com.sonicle.security.Principal sprincipal = (com.sonicle.security.Principal) principals
                    .getPrimaryPrincipal();
            if (sprincipal.isImpersonated()) {
                return "admin!" + sprincipal.getUserId() + "@" + sprincipal.getDomainId();
            }
        }
        return principals;
    }

    private Principal authenticateUser(String domainId, String internetDomain, String username, char[] password)
            throws AuthenticationException {
        WebTopApp wta = WebTopApp.getInstance();
        WebTopManager wtMgr = wta.getWebTopManager();
        AuthenticationDomain authAd = null, priAd = null;
        boolean autoCreate = false, impersonate = false;

        try {
            DirectoryManager dirManager = DirectoryManager.getManager();

            // Defines authentication domains for the auth phase and for 
            // building the right principal
            logger.debug("Building the authentication domain");
            if (isSysAdmin(internetDomain, username)) {
                impersonate = false;
                authAd = priAd = wtMgr.createSysAdminAuthenticationDomain();

            } else {
                if (wta.isInMaintenance())
                    throw new MaintenanceException("Maintenance is active. Only sys-admin can login.");
                ODomain domain = null;
                if (!StringUtils.isBlank(internetDomain)) {
                    List<ODomain> domains = wtMgr.listByInternetDomain(internetDomain);
                    if (domains.isEmpty())
                        throw new WTException("No enabled domains match specified internet domain [{}]",
                                internetDomain);
                    if (domains.size() != 1)
                        throw new WTException("Multiple domains match specified internet domain [{}]",
                                internetDomain);
                    domain = domains.get(0);
                } else {
                    domain = wtMgr.getDomain(domainId);
                    if ((domain == null) || !domain.getEnabled())
                        throw new WTException("Domain not found [{}]", domainId);
                }

                if (isSysAdminImpersonate(username)) {
                    impersonate = true;
                    authAd = wtMgr.createSysAdminAuthenticationDomain();
                    priAd = wtMgr.createAuthenticationDomain(domain);
                } else if (isDomainAdminImpersonate(username)) {
                    impersonate = true;
                    authAd = priAd = wtMgr.createAuthenticationDomain(domain);
                } else {
                    impersonate = false;
                    authAd = priAd = wtMgr.createAuthenticationDomain(domain);
                }
                autoCreate = domain.getUserAutoCreation();
            }

            DirectoryOptions opts = wta.createDirectoryOptions(authAd);
            AbstractDirectory directory = dirManager.getDirectory(authAd.getDirUri().getScheme());
            if (directory == null)
                throw new WTException("Directory not supported [{}]", authAd.getDirUri().getScheme());

            // Prepare principal for authentication
            String authUsername = impersonate ? "admin" : directory.sanitizeUsername(opts, username);
            Principal authPrincipal = new Principal(authAd, impersonate, authAd.getDomainId(), authUsername,
                    password);
            logger.debug("Authenticating principal [{}, {}]", authPrincipal.getDomainId(),
                    authPrincipal.getUserId());
            AuthUser userEntry = directory.authenticate(opts, authPrincipal);

            // Authentication phase passed succesfully, now build the right principal!
            Principal principal = null;
            if (impersonate) {
                String impUsername = sanitizeImpersonateUsername(username);
                principal = new Principal(priAd, impersonate, priAd.getDomainId(), impUsername, password);

                UserProfileId pid = new UserProfileId(principal.getDomainId(), principal.getUserId());
                OUser ouser = wta.getWebTopManager().getUser(pid);
                // We cannot continue if the user is not present, impersonation needs it!
                if (ouser == null)
                    throw new WTException("User not found [{}]", pid.toString());
                principal.setDisplayName(ouser.getDisplayName());

            } else {
                // Authentication result points to the right userId...
                principal = new Principal(priAd, impersonate, priAd.getDomainId(), userEntry.userId, password);
                principal.setDisplayName(StringUtils.defaultIfBlank(userEntry.displayName, userEntry.userId));
            }

            if (autoCreate)
                principal.pushDirectoryEntry(userEntry);
            return principal;

        } catch (URISyntaxException | WTException | DirectoryException ex) {
            logger.error("Authentication error", ex);
            throw new AuthenticationException(ex);
        }
    }

    public void checkUser(Principal principal) throws WTException {
        WebTopApp wta = WebTopApp.getInstance();
        WebTopManager wtMgr = wta.getWebTopManager();
        AuthUser userEntry = principal.popDirectoryEntry();

        synchronized (lock1) {
            WebTopManager.CheckUserResult chk = wtMgr.checkUser(principal.getDomainId(), principal.getUserId());
            if (!chk.exist) {
                if (userEntry != null) {
                    logger.debug("Creating user [{}]", principal.getSubjectId());
                    try {
                        wtMgr.addUser(false, createUserEntity(principal.getDomainId(), userEntry), false, null);
                    } catch (WTMultiCauseWarnException ex1) {
                        // This kind of exception collects errors from inner service handlers
                        logger.warn(
                                "User configuration may not have been fully completed. Please check log details above. [{}]",
                                principal.getSubjectId(), ex1);
                    }

                } else {
                    throw new WTException("User does not exist [{}]", principal.getSubjectId());
                }

            } else if (chk.exist && !chk.enabled) {
                throw new WTException("User is disabled [{}]", principal.getSubjectId());
            }
        }
    }

    private WTAuthorizationInfo loadAuthorizationInfo(Principal principal) throws Exception {
        WebTopApp wta = WebTopApp.getInstance();
        WebTopManager wtMgr = wta.getWebTopManager();
        UserProfileId pid = new UserProfileId(principal.getDomainId(), principal.getUserId());

        HashSet<String> roles = new HashSet<>();
        HashSet<String> perms = new HashSet<>();

        if (Principal.xisAdmin(pid.toString())) {
            roles.add(WebTopManager.ROLEUID_SYSADMIN);
            roles.add(WebTopManager.ROLEUID_WTADMIN);
            //perms.add(ServicePermission.permissionString(ServicePermission.namespacedName(CoreManifest.ID, "SYSADMIN"), ServicePermission.ACTION_ACCESS, "*"));
            //perms.add(ServicePermission.permissionString(ServicePermission.namespacedName(CoreManifest.ID, "WTADMIN"), ServicePermission.ACTION_ACCESS, "*"));
        } else if (principal.isImpersonated()) {
            roles.add(WebTopManager.ROLEUID_IMPERSONATED_USER);
            //perms.add(ServicePermission.permissionString(ServicePermission.namespacedName(CoreManifest.ID, "WTADMIN"), ServicePermission.ACTION_ACCESS, "*"));
        }

        // Force core private service permission for any principal
        String authRes = ServicePermission.namespacedName(CoreManifest.ID, "SERVICE");
        perms.add(ServicePermission.permissionString(authRes, ServicePermission.ACTION_ACCESS, CoreManifest.ID));

        Set<RoleWithSource> userRoles = wtMgr.getComputedRolesByUser(pid, true, true);
        for (RoleWithSource role : userRoles) {
            roles.add(role.getRoleUid());

            List<ORolePermission> rolePerms = wtMgr.listRolePermissions(role.getRoleUid());
            for (ORolePermission perm : rolePerms) {
                // Generate resource namespaced name:
                // resource "TEST" for service "com.sonicle.webtop.core" 
                // will become "com.sonicle.webtop.core.TEST"
                authRes = ServicePermission.namespacedName(perm.getServiceId(), perm.getKey());
                // Generate permission string that shiro can understand 
                // under the form: {resource}:{action}:{instance}
                perms.add(ServicePermission.permissionString(authRes, perm.getAction(), perm.getInstance()));
            }
        }
        return new WTAuthorizationInfo(roles, perms);
    }

    private boolean isSysAdmin(String internetDomain, String username) {
        return StringUtils.equals(StringUtils.lowerCase(username), "admin") && StringUtils.isBlank(internetDomain);
    }

    private String sanitizeImpersonateUsername(String username) {
        String s = StringUtils.removeStart(username, "admin!");
        return StringUtils.removeStart(s, "admin$");
    }

    private boolean isSysAdminImpersonate(String username) {
        return StringUtils.startsWith(StringUtils.lowerCase(username), "admin!");
    }

    private boolean isDomainAdminImpersonate(String username) {
        return StringUtils.startsWith(StringUtils.lowerCase(username), "admin$");
    }

    private UserEntity createUserEntity(String domainId, AuthUser userEntry) {
        UserEntity ue = new UserEntity();
        ue.setDomainId(domainId);
        ue.setUserId(userEntry.userId);
        ue.setEnabled(true);
        ue.setFirstName(userEntry.firstName);
        ue.setLastName(userEntry.lastName);
        ue.setDisplayName(userEntry.displayName);
        return ue;
    }
}