001/* 002 * Copyright 2016 Anyware Services 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 */ 016package org.ametys.plugins.core.ui.user; 017 018import java.io.IOException; 019import java.nio.charset.StandardCharsets; 020import java.security.MessageDigest; 021import java.security.NoSuchAlgorithmException; 022import java.util.ArrayList; 023import java.util.List; 024 025import org.apache.avalon.framework.logger.AbstractLogEnabled; 026import org.apache.avalon.framework.service.ServiceException; 027import org.apache.avalon.framework.service.ServiceManager; 028import org.apache.avalon.framework.service.Serviceable; 029import org.apache.cocoon.ProcessingException; 030import org.apache.commons.codec.binary.Hex; 031import org.apache.commons.lang3.StringUtils; 032import org.apache.excalibur.source.Source; 033import org.apache.excalibur.source.SourceResolver; 034import org.apache.http.NameValuePair; 035import org.apache.http.client.utils.URIBuilder; 036import org.apache.http.message.BasicNameValuePair; 037 038import org.ametys.core.user.User; 039import org.ametys.core.user.UserIdentity; 040import org.ametys.core.user.UserManager; 041 042/** 043 * Image provider working in safe mode 044 */ 045public class SafeProfileImageProvider extends AbstractLogEnabled implements ProfileImageProvider, Serviceable 046{ 047 /** Relative path of the user profiles directory, which contains all the image subdirectories */ 048 protected static final String __USER_PROFILES_DIR_PATH = "user-profiles"; 049 050 /** Name of the default image */ 051 protected static final String __DEFAULT_FILE_NAME = "default.png"; 052 053 /** Source resolver */ 054 protected SourceResolver _sourceResolver; 055 056 /** Users manager */ 057 protected UserManager _userManager; 058 059 @Override 060 public void service(ServiceManager smanager) throws ServiceException 061 { 062 _sourceResolver = (SourceResolver) smanager.lookup(SourceResolver.ROLE); 063 _userManager = (UserManager) smanager.lookup(UserManager.ROLE); 064 } 065 066 public UserProfileImage getImage(UserIdentity user, String imageSource, int size, int maxSize) throws ProcessingException 067 { 068 UserProfileImage image = getGravatarImage(user, size > 0 ? size : maxSize); 069 if (image == null) 070 { 071 return getDefaultImage(); 072 } 073 else 074 { 075 return image; 076 } 077 } 078 079 /** 080 * Get the default user image 081 * @return The UserProfileImage for the default image 082 */ 083 protected UserProfileImage getDefaultImage() 084 { 085 String location = "plugin:core-ui://resources/img/" + __USER_PROFILES_DIR_PATH + "/" + __DEFAULT_FILE_NAME; 086 Source imgSource = null; 087 088 try 089 { 090 imgSource = _sourceResolver.resolveURI(location); 091 if (imgSource.exists()) 092 { 093 return new UserProfileImage(imgSource.getInputStream(), __DEFAULT_FILE_NAME, null); 094 } 095 096 if (getLogger().isWarnEnabled()) 097 { 098 getLogger().warn("Unable to find the default user image"); 099 } 100 } 101 catch (IOException e) 102 { 103 getLogger().error("Unable to retrieve the default user image"); 104 } 105 finally 106 { 107 if (imgSource != null) 108 { 109 _sourceResolver.release(imgSource); 110 } 111 } 112 113 return null; 114 } 115 116 /** 117 * Get gravatar image 118 * @param user The user 119 * @param size The image size 120 * @return The UserProfileImage or null if not found 121 */ 122 protected UserProfileImage getGravatarImage(UserIdentity user, int size) 123 { 124 // Resolve an http source 125 Source httpSource = null; 126 try 127 { 128 httpSource = _getGravatarImageSource(user, size); 129 if (httpSource != null && httpSource.exists()) 130 { 131 return new UserProfileImage(httpSource.getInputStream()); 132 } 133 } 134 catch (IOException e) 135 { 136 getLogger().error("Unable to retrieve gravatar image for user '" + user + "'.", e); 137 } 138 finally 139 { 140 if (httpSource != null) 141 { 142 _sourceResolver.release(httpSource); 143 } 144 } 145 146 return null; 147 } /** 148 * Get the source of a gravatar image 149 * @param userIdentity The user 150 * @param size The requested size 151 * @return The source or null 152 * @throws IOException If an error occurs while resolving the source uri 153 */ 154 private Source _getGravatarImageSource(UserIdentity userIdentity, Integer size) throws IOException 155 { 156 User user = _userManager.getUser(userIdentity.getPopulationId(), userIdentity.getLogin()); 157 if (user == null) 158 { 159 if (getLogger().isWarnEnabled()) 160 { 161 getLogger().warn("Unable to get gravatar image source - user not found " + userIdentity); 162 } 163 return null; 164 } 165 166 String email = user.getEmail(); 167 if (StringUtils.isEmpty(email)) 168 { 169 if (getLogger().isInfoEnabled()) 170 { 171 getLogger().info(String.format("Unable to get gravatar image for user '%s' - an email is mandatory", userIdentity)); 172 } 173 return null; 174 } 175 176 // Compute hex MD5 hash 177 String hash = null; 178 try 179 { 180 MessageDigest md5 = MessageDigest.getInstance("MD5"); 181 md5.reset(); 182 md5.update(StandardCharsets.UTF_8.encode(email)); 183 byte[] hexBytes = new Hex(StandardCharsets.UTF_8).encode(md5.digest()); 184 hash = new String(hexBytes, StandardCharsets.UTF_8); 185 } 186 catch (NoSuchAlgorithmException e) 187 { 188 // This error exception not be raised since MD5 is embedded in the JDK 189 getLogger().error("Cannot encode the user email to md5Base64", e); 190 return null; 191 } 192 193 // Build gravatar URL request 194 List<NameValuePair> qparams = new ArrayList<>(1); 195 qparams.add(new BasicNameValuePair("d", "404")); // 404 if no image for this user 196 197 if (size != null && size > 0) 198 { 199 qparams.add(new BasicNameValuePair("s", Integer.toString(size))); 200 } 201 202 String uri = new URIBuilder() 203 .setScheme("http") 204 .setHost("www.gravatar.com") 205 .setPath("/avatar/" + hash + ".png") // force png 206 .setParameters(qparams).toString(); 207 208 if (getLogger().isDebugEnabled()) 209 { 210 getLogger().debug(String.format("Build gravatar uri for user '%s' : %s", userIdentity, uri)); 211 } 212 213 return _sourceResolver.resolveURI(uri); 214 } 215 216 /** 217 * Test if the gravatar image exists 218 * @param user The user 219 * @return True if the image exists 220 */ 221 public boolean hasGravatarImage(UserIdentity user) 222 { 223 Source httpSource = null; 224 try 225 { 226 httpSource = _getGravatarImageSource(user, null); 227 return httpSource != null && httpSource.exists(); 228 } 229 catch (IOException e) 230 { 231 getLogger().error("Unable to test the gravatar image for user '" + user + "'.", e); 232 } 233 finally 234 { 235 if (httpSource != null) 236 { 237 _sourceResolver.release(httpSource); 238 } 239 } 240 241 return false; 242 } 243}