001/* 002 * (C) Copyright 2010-2011 Nuxeo SA (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * Gagnavarslan ehf 016 */ 017package org.nuxeo.ecm.ui.web.auth.digest; 018 019import java.util.Map; 020 021import org.apache.commons.codec.digest.DigestUtils; 022import org.apache.commons.lang.StringUtils; 023import org.apache.commons.logging.Log; 024import org.apache.commons.logging.LogFactory; 025import org.nuxeo.ecm.core.api.DocumentModel; 026import org.nuxeo.ecm.directory.Directory; 027import org.nuxeo.ecm.directory.DirectoryException; 028import org.nuxeo.ecm.directory.Session; 029import org.nuxeo.ecm.directory.api.DirectoryService; 030import org.nuxeo.ecm.platform.api.login.UserIdentificationInfo; 031import org.nuxeo.ecm.platform.login.BaseLoginModule; 032import org.nuxeo.ecm.platform.usermanager.UserManager; 033import org.nuxeo.runtime.api.Framework; 034 035/** 036 * Nuxeo Login Plugin for HTTP Digest Access Authentication (RFC 2617). 037 */ 038public class DigestLoginPlugin extends BaseLoginModule { 039 040 private static final Log log = LogFactory.getLog(DigestLoginPlugin.class); 041 042 protected static final String REALM = "realm"; 043 044 protected static final String HTTP_METHOD = "httpMethod"; 045 046 protected static final String URI = "uri"; 047 048 protected static final String QOP = "qop"; 049 050 protected static final String NONCE = "nonce"; 051 052 protected static final String NC = "nc"; 053 054 protected static final String CNONCE = "cnonce"; 055 056 protected static final String PASSWORD_FIELD = "passwordField"; 057 058 @Override 059 public Boolean initLoginModule() { 060 return Boolean.TRUE; 061 } 062 063 @Override 064 public String validatedUserIdentity(UserIdentificationInfo userIdent) { 065 try { 066 String storedHA1 = getStoredHA1(userIdent.getUserName()); 067 068 if (StringUtils.isEmpty(storedHA1)) { 069 log.warn("Digest authentication failed. Stored HA1 is empty"); 070 return null; 071 } 072 073 Map<String, String> loginParameters = userIdent.getLoginParameters(); 074 String generateDigest = generateDigest(storedHA1, loginParameters.get(HTTP_METHOD), // 075 loginParameters.get(URI), // 076 loginParameters.get(QOP), // RFC 2617 extension 077 loginParameters.get(NONCE), // 078 loginParameters.get(NC), // RFC 2617 extension 079 loginParameters.get(CNONCE) // RFC 2617 extension 080 ); 081 082 if (generateDigest.equals(userIdent.getPassword())) { 083 return userIdent.getUserName(); 084 } else { 085 log.warn("Digest authentication failed for user: " + userIdent.getUserName() + " realm: " 086 + loginParameters.get(REALM)); 087 return null; 088 } 089 } catch (IllegalArgumentException | DirectoryException e) { 090 log.error("Digest authentication failed", e); 091 return null; 092 } 093 } 094 095 public static String generateDigest(String ha1, String httpMethod, String uri, String qop, String nonce, String nc, 096 String cnonce) throws IllegalArgumentException { 097 String a2 = httpMethod + ":" + uri; 098 String ha2 = DigestUtils.md5Hex(a2); 099 String digest; 100 if (qop == null) { 101 digest = ha1 + ":" + nonce + ":" + ha2; 102 } else if ("auth".equals(qop)) { 103 digest = ha1 + ":" + nonce + ":" + nc + ":" + cnonce + ":" + qop + ":" + ha2; 104 } else { 105 throw new IllegalArgumentException("This method does not support a qop: '" + qop + "'"); 106 } 107 return DigestUtils.md5Hex(digest); 108 } 109 110 public static String encodeDigestAuthPassword(String username, String realm, String password) { 111 String a1 = username + ":" + realm + ":" + password; 112 return DigestUtils.md5Hex(a1); 113 } 114 115 protected String getStoredHA1(String username) throws DirectoryException { 116 UserManager userManager = Framework.getService(UserManager.class); 117 String dirName = userManager.getDigestAuthDirectory(); 118 DirectoryService directoryService = Framework.getLocalService(DirectoryService.class); 119 Directory directory = directoryService.getDirectory(dirName); 120 if (directory == null) { 121 throw new IllegalArgumentException("Digest Auth directory not found: " + dirName); 122 } 123 try (Session dir = directoryService.open(dirName)) { 124 String schema = directoryService.getDirectorySchema(dirName); 125 DocumentModel entry = dir.getEntry(username, true); 126 String passwordField = (parameters.containsKey(PASSWORD_FIELD)) ? parameters.get(PASSWORD_FIELD) 127 : dir.getPasswordField(); 128 return entry == null ? null : (String) entry.getProperty(schema, passwordField); 129 } 130 } 131 132}