001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software GmbH & Co. KG, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.security; 029 030import org.opencms.crypto.CmsEncryptionException; 031import org.opencms.file.CmsGroup; 032import org.opencms.file.CmsObject; 033import org.opencms.file.CmsUser; 034import org.opencms.i18n.CmsEncoder; 035import org.opencms.main.A_CmsAuthorizationHandler; 036import org.opencms.main.CmsException; 037import org.opencms.main.CmsHttpAuthenticationSettings; 038import org.opencms.main.OpenCms; 039import org.opencms.ui.login.CmsLoginHelper; 040import org.opencms.util.CmsMacroResolver; 041import org.opencms.util.CmsRequestUtil; 042import org.opencms.util.CmsStringUtil; 043import org.opencms.workplace.CmsWorkplaceManager; 044import org.opencms.workplace.CmsWorkplaceSettings; 045 046import java.io.IOException; 047import java.util.ArrayList; 048import java.util.Arrays; 049import java.util.List; 050import java.util.Set; 051import java.util.stream.Collectors; 052 053import javax.servlet.http.HttpServletRequest; 054import javax.servlet.http.HttpServletResponse; 055import javax.servlet.http.HttpSession; 056 057import org.apache.commons.codec.binary.Base64; 058 059import com.google.common.base.Joiner; 060 061/** 062 * Defines default authorization methods.<p> 063 * 064 * @since 6.5.4 065 */ 066public class CmsDefaultAuthorizationHandler extends A_CmsAuthorizationHandler { 067 068 /** Configuration parameter to control for which paths startup settings should be applied after HTTP Basic authentication. */ 069 public static final String PARAM_HTTP_BASICAUTH_USESTARTSETTINGS_PATHS = "http.basicauth.usestartsettings.paths"; 070 071 /** Configuration parameter to control for which users startup settings should be applied after HTTP Basic authentication. */ 072 public static final String PARAM_HTTP_BASICAUTH_USESTARTSETTINGS_USERS = "http.basicauth.usestartsettings.users"; 073 074 /** Basic authorization prefix constant. */ 075 public static final String AUTHORIZATION_BASIC_PREFIX = "BASIC "; 076 /** Authorization header constant. */ 077 public static final String HEADER_AUTHORIZATION = "Authorization"; 078 079 /** Parameter for passing the encrypted version of the requested resource. */ 080 public static final String PARAM_ENCRYPTED_REQUESTED_RESOURCE = "encryptedRequestedResource"; 081 082 /** Credentials separator constant. */ 083 public static final String SEPARATOR_CREDENTIALS = ":"; 084 085 /** 086 * Checks if a request URI path matches a given set of prefix paths. 087 * 088 * @param uri the request URI path 089 * @param pathSpec a comma separated list of path prefixes, which may contain %(contextPath) macros 090 * @return true if the URI path matches the path spec 091 */ 092 protected static boolean checkPath(String uri, String pathSpec) { 093 094 if (CmsStringUtil.isEmptyOrWhitespaceOnly(pathSpec)) { 095 return false; 096 } 097 CmsMacroResolver resolver = new CmsMacroResolver(); 098 pathSpec = resolver.resolveMacros(pathSpec); 099 String[] pathPatterns = pathSpec.split(","); 100 for (String pathToken : pathPatterns) { 101 if (CmsStringUtil.isPrefixPath(pathToken, uri)) { 102 return true; 103 } 104 } 105 return false; 106 } 107 108 /** 109 * Checks if the authenticated user matches a user specification string. 110 * 111 * <p>The user specification string is a comma-separed list of entries of the form TYPE.Name, where 112 * TYPE is either ROLE, GROUP, or USER. The method returns true if the user matches any of the groups, roles, or user names from this list. 113 * 114 * <p>It's also possible to configure an entry "*", which always matches. 115 * 116 * @param cms the CMS context 117 * @param userSpec the user specification 118 * @return true if the user matches any entry from the user specification 119 */ 120 protected static boolean checkUser(CmsObject cms, String userSpec) { 121 122 if (CmsStringUtil.isEmptyOrWhitespaceOnly(userSpec)) { 123 return false; 124 } 125 126 Set<String> groupsOfUser = null; // lazily initialized 127 String[] entries = userSpec.split(","); 128 for (String userSpecEntry : entries) { 129 userSpecEntry = userSpecEntry.trim(); 130 if ("*".equals(userSpecEntry)) { 131 return true; 132 } else if (userSpecEntry.startsWith(I_CmsPrincipal.PRINCIPAL_USER)) { 133 String userName = CmsUser.removePrefix(userSpecEntry); 134 if (cms.getRequestContext().getCurrentUser().getName().equals(userName)) { 135 return true; 136 } 137 } else if (userSpecEntry.startsWith(CmsRole.PRINCIPAL_ROLE)) { 138 String actualRole = CmsRole.removePrefix(userSpecEntry); 139 CmsRole roleObj = null; 140 if (actualRole.contains("/")) { 141 roleObj = CmsRole.valueOfRoleName(actualRole); 142 } else { 143 roleObj = CmsRole.valueOfRoleName(actualRole).forOrgUnit(null); 144 } 145 if (OpenCms.getRoleManager().hasRole(cms, roleObj)) { 146 return true; 147 } 148 } else if (userSpecEntry.startsWith(I_CmsPrincipal.PRINCIPAL_GROUP)) { 149 String groupName = CmsGroup.removePrefix(userSpecEntry); 150 151 if (groupsOfUser == null) { 152 try { 153 groupsOfUser = cms.getGroupsOfUser( 154 cms.getRequestContext().getCurrentUser().getName(), 155 false).stream().map(group -> group.getName()).collect(Collectors.toSet()); 156 } catch (Exception e) { 157 LOG.error(e.getLocalizedMessage(), e); 158 continue; 159 } 160 } 161 if (groupsOfUser.contains(groupName)) { 162 return true; 163 } 164 } 165 } 166 return false; 167 } 168 169 /** 170 * @see org.opencms.security.I_CmsAuthorizationHandler#getLoginFormURL(java.lang.String, java.lang.String, java.lang.String) 171 */ 172 public String getLoginFormURL(String loginFormURL, String params, String callbackURL) { 173 174 if (loginFormURL != null) { 175 176 StringBuffer fullURL = new StringBuffer(loginFormURL); 177 if (callbackURL != null) { 178 fullURL.append("?"); 179 fullURL.append(CmsWorkplaceManager.PARAM_LOGIN_REQUESTED_RESOURCE); 180 fullURL.append("="); 181 fullURL.append(callbackURL); 182 } 183 List<String> paramList; 184 if (params != null) { 185 paramList = new ArrayList<>(Arrays.asList(params.split("&"))); 186 } else { 187 paramList = new ArrayList<>(); 188 } 189 if (callbackURL != null) { 190 try { 191 paramList.add( 192 PARAM_ENCRYPTED_REQUESTED_RESOURCE 193 + "=" 194 + OpenCms.getDefaultTextEncryption().encrypt(CmsEncoder.decode(callbackURL))); 195 } catch (CmsEncryptionException e) { 196 LOG.error(e.getLocalizedMessage(), e); 197 } 198 } 199 fullURL.append((callbackURL != null) ? "&" : "?"); 200 fullURL.append(Joiner.on("&").join(paramList)); 201 return fullURL.toString(); 202 } 203 204 return null; 205 } 206 207 /** 208 * @see I_CmsAuthorizationHandler#initCmsObject(HttpServletRequest) 209 */ 210 public CmsObject initCmsObject(HttpServletRequest request) { 211 212 // check if "basic" authorization data is provided 213 CmsObject cms = checkBasicAuthorization(request); 214 // basic authorization successful? 215 if (cms != null) { 216 try { 217 // register the session into OpenCms and 218 // return successful logged in user 219 return registerSession(request, cms); 220 } catch (CmsException e) { 221 // ignore and threat the whole login process as failed 222 } 223 } 224 // failed 225 return null; 226 } 227 228 /** 229 * @see org.opencms.security.I_CmsAuthorizationHandler#initCmsObject(javax.servlet.http.HttpServletRequest, org.opencms.security.I_CmsAuthorizationHandler.I_PrivilegedLoginAction) 230 */ 231 public CmsObject initCmsObject( 232 HttpServletRequest request, 233 I_CmsAuthorizationHandler.I_PrivilegedLoginAction loginAction) { 234 235 return initCmsObject(request); 236 } 237 238 /** 239 * @see I_CmsAuthorizationHandler#initCmsObject(HttpServletRequest, String, String) 240 */ 241 public CmsObject initCmsObject(HttpServletRequest request, String userName, String pwd) throws CmsException { 242 243 // first, try to validate the session 244 CmsObject cms = initCmsObjectFromSession(request); 245 if (cms != null) { 246 return cms; 247 } 248 // try to login with the given credentials 249 cms = OpenCms.initCmsObject(OpenCms.getDefaultUsers().getUserGuest()); 250 // this will throw an exception if login fails 251 cms.loginUser(userName, pwd); 252 // register the session into OpenCms and 253 // return successful logged in user 254 return registerSession(request, cms); 255 } 256 257 /** 258 * This method sends a request to the client to display a login form, 259 * it is needed for HTTP-Authentication.<p> 260 * 261 * @param req the client request 262 * @param res the response 263 * @param loginFormURL the full URL used for form based authentication 264 * 265 * @throws IOException if something goes wrong 266 */ 267 public void requestAuthorization(HttpServletRequest req, HttpServletResponse res, String loginFormURL) 268 throws IOException { 269 270 CmsHttpAuthenticationSettings httpAuthenticationSettings = OpenCms.getSystemInfo().getHttpAuthenticationSettings(); 271 272 if (loginFormURL == null) { 273 if (httpAuthenticationSettings.useBrowserBasedHttpAuthentication()) { 274 // HTTP basic authentication is used 275 res.setHeader( 276 CmsRequestUtil.HEADER_WWW_AUTHENTICATE, 277 "BASIC realm=\"" + OpenCms.getSystemInfo().getServerName() + "\""); 278 res.setStatus(HttpServletResponse.SC_UNAUTHORIZED); 279 return; 280 281 } else if (httpAuthenticationSettings.getFormBasedHttpAuthenticationUri() != null) { 282 loginFormURL = httpAuthenticationSettings.getFormBasedHttpAuthenticationUri(); 283 } else { 284 LOG.error( 285 Messages.get().getBundle().key( 286 Messages.ERR_UNSUPPORTED_AUTHENTICATION_MECHANISM_1, 287 httpAuthenticationSettings.getBrowserBasedAuthenticationMechanism())); 288 res.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); 289 return; 290 } 291 } 292 293 if (LOG.isDebugEnabled()) { 294 LOG.debug( 295 Messages.get().getBundle().key( 296 Messages.LOG_AUTHENTICATE_PROPERTY_2, 297 loginFormURL, 298 req.getRequestURI())); 299 } 300 // finally redirect to the login form 301 res.sendRedirect(loginFormURL); 302 } 303 304 /** 305 * Checks if the current request contains HTTP basic authentication information in 306 * the headers, if so the user is tried to log in with this data, and on success a 307 * session is generated.<p> 308 * 309 * @param req the current HTTP request 310 * 311 * @return the authenticated cms object, or <code>null</code> if failed 312 */ 313 protected CmsObject checkBasicAuthorization(HttpServletRequest req) { 314 315 if (LOG.isDebugEnabled()) { 316 LOG.debug("Checking for basic authorization."); 317 } 318 try { 319 CmsObject cms = OpenCms.initCmsObject(OpenCms.getDefaultUsers().getUserGuest()); 320 if (OpenCms.getSystemInfo().getHttpAuthenticationSettings().getBrowserBasedAuthenticationMechanism() == null) { 321 // browser base authorization is not enabled, return Guest user CmsObject 322 if (LOG.isDebugEnabled()) { 323 LOG.debug("Browser based authorization not enabled."); 324 } 325 return cms; 326 } 327 // no user identified from the session and basic authentication is enabled 328 String auth = req.getHeader(HEADER_AUTHORIZATION); 329 if ((auth == null) || !auth.toUpperCase().startsWith(AUTHORIZATION_BASIC_PREFIX)) { 330 // no authorization data is available 331 return cms; 332 } 333 // get encoded user and password, following after "BASIC " 334 String base64Token = auth.substring(6); 335 336 // decode it, using base 64 decoder 337 String token = new String(Base64.decodeBase64(base64Token.getBytes())); 338 String username = null; 339 String password = null; 340 int pos = token.indexOf(SEPARATOR_CREDENTIALS); 341 if (pos != -1) { 342 username = token.substring(0, pos); 343 password = token.substring(pos + 1); 344 } 345 // authentication in the DB 346 cms.loginUser(username, password); 347 348 // authorization was successful create a session 349 HttpSession session = req.getSession(true); 350 String requestUri = req.getRequestURI(); 351 boolean isWorkplace = requestUri.startsWith(OpenCms.getSystemInfo().getWorkplaceContext()) 352 || requestUri.startsWith( 353 CmsStringUtil.joinPaths(OpenCms.getSystemInfo().getOpenCmsContext(), "/system/workplace")); 354 isWorkplace = isWorkplace && OpenCms.getRoleManager().hasRole(cms, CmsRole.ELEMENT_AUTHOR); 355 LOG.debug("isWorkplace = " + isWorkplace); 356 boolean initStartSettings = isWorkplace || shouldUseStartSettingsForHttpBasicAuth(cms, req); 357 LOG.debug("initStartSettings = " + initStartSettings); 358 OpenCms.getSiteManager().isWorkplaceRequest(req); 359 if (initStartSettings) { 360 CmsWorkplaceSettings settings = CmsLoginHelper.initSiteAndProject(cms); 361 session.setAttribute(CmsWorkplaceManager.SESSION_WORKPLACE_SETTINGS, settings); 362 } 363 364 return cms; 365 } catch (CmsException e) { 366 // authorization failed 367 return null; 368 } 369 } 370 371 /** 372 * Checks whether start settings should be used after HTTP Basic authentication. 373 * 374 * <p>This method will not be called for workplace requests; for these the start settings will always be used. 375 * 376 * @param cms the CMS context initialized with the user from the HTTP Basic authentication 377 * @param req the current request 378 * 379 * @return true if the start settings should be used 380 */ 381 protected boolean shouldUseStartSettingsForHttpBasicAuth(CmsObject cms, HttpServletRequest req) { 382 383 String userSpec = m_parameters.get(PARAM_HTTP_BASICAUTH_USESTARTSETTINGS_USERS); 384 String pathSpec = m_parameters.get(PARAM_HTTP_BASICAUTH_USESTARTSETTINGS_PATHS); 385 386 if (!checkPath(req.getRequestURI(), pathSpec)) { 387 LOG.debug("checkPath returned false for " + req.getRequestURI() + ", pathSpec=" + pathSpec); 388 return false; 389 } 390 if (!checkUser(cms, userSpec)) { 391 LOG.debug( 392 "checkUser returned false for " 393 + cms.getRequestContext().getCurrentUser().getName() 394 + ", userSpec = " 395 + userSpec); 396 return false; 397 } 398 399 return true; 400 } 401}