Java tutorial
/* * 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; } }