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}