001/* 002 * (C) Copyright 2010-2011 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * Gagnavarslan ehf 018 */ 019package org.nuxeo.ecm.ui.web.auth.digest; 020 021import java.util.Map; 022 023import org.apache.commons.codec.digest.DigestUtils; 024import org.apache.commons.lang.StringUtils; 025import org.apache.commons.logging.Log; 026import org.apache.commons.logging.LogFactory; 027import org.nuxeo.ecm.core.api.DocumentModel; 028import org.nuxeo.ecm.directory.Directory; 029import org.nuxeo.ecm.directory.DirectoryException; 030import org.nuxeo.ecm.directory.Session; 031import org.nuxeo.ecm.directory.api.DirectoryService; 032import org.nuxeo.ecm.platform.api.login.UserIdentificationInfo; 033import org.nuxeo.ecm.platform.login.BaseLoginModule; 034import org.nuxeo.ecm.platform.usermanager.UserManager; 035import org.nuxeo.runtime.api.Framework; 036 037/** 038 * Nuxeo Login Plugin for HTTP Digest Access Authentication (RFC 2617). 039 */ 040public class DigestLoginPlugin extends BaseLoginModule { 041 042 private static final Log log = LogFactory.getLog(DigestLoginPlugin.class); 043 044 protected static final String REALM = "realm"; 045 046 protected static final String HTTP_METHOD = "httpMethod"; 047 048 protected static final String URI = "uri"; 049 050 protected static final String QOP = "qop"; 051 052 protected static final String NONCE = "nonce"; 053 054 protected static final String NC = "nc"; 055 056 protected static final String CNONCE = "cnonce"; 057 058 protected static final String PASSWORD_FIELD = "passwordField"; 059 060 @Override 061 public Boolean initLoginModule() { 062 return Boolean.TRUE; 063 } 064 065 @Override 066 public String validatedUserIdentity(UserIdentificationInfo userIdent) { 067 try { 068 String storedHA1 = getStoredHA1(userIdent.getUserName()); 069 070 if (StringUtils.isEmpty(storedHA1)) { 071 log.warn("Digest authentication failed. Stored HA1 is empty"); 072 return null; 073 } 074 075 Map<String, String> loginParameters = userIdent.getLoginParameters(); 076 String generateDigest = generateDigest(storedHA1, loginParameters.get(HTTP_METHOD), // 077 loginParameters.get(URI), // 078 loginParameters.get(QOP), // RFC 2617 extension 079 loginParameters.get(NONCE), // 080 loginParameters.get(NC), // RFC 2617 extension 081 loginParameters.get(CNONCE) // RFC 2617 extension 082 ); 083 084 if (generateDigest.equals(userIdent.getPassword())) { 085 return userIdent.getUserName(); 086 } else { 087 log.warn("Digest authentication failed for user: " + userIdent.getUserName() + " realm: " 088 + loginParameters.get(REALM)); 089 return null; 090 } 091 } catch (IllegalArgumentException | DirectoryException e) { 092 log.error("Digest authentication failed", e); 093 return null; 094 } 095 } 096 097 public static String generateDigest(String ha1, String httpMethod, String uri, String qop, String nonce, String nc, 098 String cnonce) throws IllegalArgumentException { 099 String a2 = httpMethod + ":" + uri; 100 String ha2 = DigestUtils.md5Hex(a2); 101 String digest; 102 if (qop == null) { 103 digest = ha1 + ":" + nonce + ":" + ha2; 104 } else if ("auth".equals(qop)) { 105 digest = ha1 + ":" + nonce + ":" + nc + ":" + cnonce + ":" + qop + ":" + ha2; 106 } else { 107 throw new IllegalArgumentException("This method does not support a qop: '" + qop + "'"); 108 } 109 return DigestUtils.md5Hex(digest); 110 } 111 112 public static String encodeDigestAuthPassword(String username, String realm, String password) { 113 String a1 = username + ":" + realm + ":" + password; 114 return DigestUtils.md5Hex(a1); 115 } 116 117 protected String getStoredHA1(String username) throws DirectoryException { 118 UserManager userManager = Framework.getService(UserManager.class); 119 String dirName = userManager.getDigestAuthDirectory(); 120 DirectoryService directoryService = Framework.getService(DirectoryService.class); 121 Directory directory = directoryService.getDirectory(dirName); 122 if (directory == null) { 123 throw new IllegalArgumentException("Digest Auth directory not found: " + dirName); 124 } 125 try (Session dir = directoryService.open(dirName)) { 126 dir.setReadAllColumns(true); // needed to read digest password 127 String schema = directoryService.getDirectorySchema(dirName); 128 DocumentModel entry = dir.getEntry(username, true); 129 String passwordField = (parameters.containsKey(PASSWORD_FIELD)) ? parameters.get(PASSWORD_FIELD) 130 : dir.getPasswordField(); 131 return entry == null ? null : (String) entry.getProperty(schema, passwordField); 132 } 133 } 134 135}