001    /**
002     * Copyright (c) 2000-2013 Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    package com.liferay.portlet.login.action;
016    
017    import com.liferay.portal.DuplicateUserEmailAddressException;
018    import com.liferay.portal.kernel.configuration.Filter;
019    import com.liferay.portal.kernel.log.Log;
020    import com.liferay.portal.kernel.log.LogFactoryUtil;
021    import com.liferay.portal.kernel.servlet.SessionErrors;
022    import com.liferay.portal.kernel.servlet.SessionMessages;
023    import com.liferay.portal.kernel.util.CharPool;
024    import com.liferay.portal.kernel.util.Constants;
025    import com.liferay.portal.kernel.util.GetterUtil;
026    import com.liferay.portal.kernel.util.HttpUtil;
027    import com.liferay.portal.kernel.util.ParamUtil;
028    import com.liferay.portal.kernel.util.PropsKeys;
029    import com.liferay.portal.kernel.util.StringPool;
030    import com.liferay.portal.kernel.util.Validator;
031    import com.liferay.portal.model.User;
032    import com.liferay.portal.security.auth.PrincipalException;
033    import com.liferay.portal.service.ServiceContext;
034    import com.liferay.portal.service.UserLocalServiceUtil;
035    import com.liferay.portal.struts.PortletAction;
036    import com.liferay.portal.theme.ThemeDisplay;
037    import com.liferay.portal.util.OpenIdUtil;
038    import com.liferay.portal.util.PortalUtil;
039    import com.liferay.portal.util.PropsUtil;
040    import com.liferay.portal.util.PropsValues;
041    import com.liferay.portal.util.WebKeys;
042    import com.liferay.portlet.ActionResponseImpl;
043    import com.liferay.util.PwdGenerator;
044    
045    import java.net.URL;
046    
047    import java.util.Calendar;
048    import java.util.List;
049    import java.util.Locale;
050    
051    import javax.portlet.ActionRequest;
052    import javax.portlet.ActionResponse;
053    import javax.portlet.PortletConfig;
054    import javax.portlet.PortletURL;
055    import javax.portlet.RenderRequest;
056    import javax.portlet.RenderResponse;
057    
058    import javax.servlet.http.HttpServletRequest;
059    import javax.servlet.http.HttpServletResponse;
060    import javax.servlet.http.HttpSession;
061    
062    import org.apache.struts.action.ActionForm;
063    import org.apache.struts.action.ActionForward;
064    import org.apache.struts.action.ActionMapping;
065    
066    import org.openid4java.OpenIDException;
067    import org.openid4java.consumer.ConsumerManager;
068    import org.openid4java.consumer.VerificationResult;
069    import org.openid4java.discovery.DiscoveryInformation;
070    import org.openid4java.discovery.Identifier;
071    import org.openid4java.message.AuthRequest;
072    import org.openid4java.message.AuthSuccess;
073    import org.openid4java.message.MessageExtension;
074    import org.openid4java.message.ParameterList;
075    import org.openid4java.message.ax.AxMessage;
076    import org.openid4java.message.ax.FetchRequest;
077    import org.openid4java.message.ax.FetchResponse;
078    import org.openid4java.message.sreg.SRegMessage;
079    import org.openid4java.message.sreg.SRegRequest;
080    import org.openid4java.message.sreg.SRegResponse;
081    
082    /**
083     * @author Brian Wing Shun Chan
084     * @author Jorge Ferrer
085     */
086    public class OpenIdAction extends PortletAction {
087    
088            @Override
089            public void processAction(
090                            ActionMapping actionMapping, ActionForm actionForm,
091                            PortletConfig portletConfig, ActionRequest actionRequest,
092                            ActionResponse actionResponse)
093                    throws Exception {
094    
095                    ThemeDisplay themeDisplay = (ThemeDisplay)actionRequest.getAttribute(
096                            WebKeys.THEME_DISPLAY);
097    
098                    if (!OpenIdUtil.isEnabled(themeDisplay.getCompanyId())) {
099                            throw new PrincipalException();
100                    }
101    
102                    if (actionRequest.getRemoteUser() != null) {
103                            actionResponse.sendRedirect(themeDisplay.getPathMain());
104    
105                            return;
106                    }
107    
108                    String cmd = ParamUtil.getString(actionRequest, Constants.CMD);
109    
110                    try {
111                            if (cmd.equals(Constants.READ)) {
112                                    String redirect = readOpenIdResponse(
113                                            themeDisplay, actionRequest, actionResponse);
114    
115                                    if (Validator.isNull(redirect)) {
116                                            redirect =
117                                                    PortalUtil.getPortalURL(actionRequest) +
118                                                            themeDisplay.getURLSignIn();
119                                    }
120    
121                                    sendRedirect(actionRequest, actionResponse, redirect);
122                            }
123                            else {
124                                    sendOpenIdRequest(themeDisplay, actionRequest, actionResponse);
125                            }
126                    }
127                    catch (Exception e) {
128                            if (e instanceof DuplicateUserEmailAddressException) {
129                                    SessionErrors.add(actionRequest, e.getClass());
130                            }
131                            else if (e instanceof OpenIDException) {
132                                    if (_log.isInfoEnabled()) {
133                                            _log.info(
134                                                    "Error communicating with OpenID provider: " +
135                                                            e.getMessage());
136                                    }
137    
138                                    SessionErrors.add(actionRequest, e.getClass());
139                            }
140                            else {
141                                    _log.error("Error processing the OpenID login", e);
142    
143                                    PortalUtil.sendError(e, actionRequest, actionResponse);
144                            }
145                    }
146            }
147    
148            @Override
149            public ActionForward render(
150                            ActionMapping actionMapping, ActionForm actionForm,
151                            PortletConfig portletConfig, RenderRequest renderRequest,
152                            RenderResponse renderResponse)
153                    throws Exception {
154    
155                    ThemeDisplay themeDisplay = (ThemeDisplay)renderRequest.getAttribute(
156                            WebKeys.THEME_DISPLAY);
157    
158                    if (!OpenIdUtil.isEnabled(themeDisplay.getCompanyId())) {
159                            return actionMapping.findForward("portlet.login.login");
160                    }
161    
162                    renderResponse.setTitle(themeDisplay.translate("open-id"));
163    
164                    return actionMapping.findForward("portlet.login.open_id");
165            }
166    
167            protected String getFirstValue(List<String> values) {
168                    if ((values == null) || (values.size() < 1)) {
169                            return null;
170                    }
171    
172                    return values.get(0);
173            }
174    
175            protected String getOpenIdProvider(URL endpointURL) {
176                    String hostName = endpointURL.getHost();
177    
178                    for (String openIdProvider : PropsValues.OPEN_ID_PROVIDERS) {
179                            String openIdURLString = PropsUtil.get(
180                                    PropsKeys.OPEN_ID_URL, new Filter(openIdProvider));
181    
182                            if (hostName.equals(openIdURLString)) {
183                                    return openIdProvider;
184                            }
185                    }
186    
187                    return _OPEN_ID_PROVIDER_DEFAULT;
188            }
189    
190            @Override
191            protected boolean isCheckMethodOnProcessAction() {
192                    return _CHECK_METHOD_ON_PROCESS_ACTION;
193            }
194    
195            protected String readOpenIdResponse(
196                            ThemeDisplay themeDisplay, ActionRequest actionRequest,
197                            ActionResponse actionResponse)
198                    throws Exception {
199    
200                    HttpServletRequest request = PortalUtil.getHttpServletRequest(
201                            actionRequest);
202                    HttpSession session = request.getSession();
203    
204                    ConsumerManager consumerManager = OpenIdUtil.getConsumerManager();
205    
206                    ParameterList parameterList = new ParameterList(
207                            actionRequest.getParameterMap());
208    
209                    DiscoveryInformation discoveryInformation =
210                            (DiscoveryInformation)session.getAttribute(WebKeys.OPEN_ID_DISCO);
211    
212                    if (discoveryInformation == null) {
213                            return null;
214                    }
215    
216                    String receivingURL = ParamUtil.getString(
217                            actionRequest, "openid.return_to");
218    
219                    VerificationResult verificationResult = consumerManager.verify(
220                            receivingURL, parameterList, discoveryInformation);
221    
222                    Identifier identifier = verificationResult.getVerifiedId();
223    
224                    if (identifier == null) {
225                            return null;
226                    }
227    
228                    AuthSuccess authSuccess =
229                            (AuthSuccess)verificationResult.getAuthResponse();
230    
231                    String firstName = null;
232                    String lastName = null;
233                    String emailAddress = null;
234    
235                    if (authSuccess.hasExtension(SRegMessage.OPENID_NS_SREG)) {
236                            MessageExtension messageExtension = authSuccess.getExtension(
237                                    SRegMessage.OPENID_NS_SREG);
238    
239                            if (messageExtension instanceof SRegResponse) {
240                                    SRegResponse sregResp = (SRegResponse)messageExtension;
241    
242                                    String fullName = GetterUtil.getString(
243                                            sregResp.getAttributeValue(_OPEN_ID_SREG_ATTR_FULLNAME));
244    
245                                    String[] names = splitFullName(fullName);
246    
247                                    if (names != null) {
248                                            firstName = names[0];
249                                            lastName = names[1];
250                                    }
251    
252                                    emailAddress = sregResp.getAttributeValue(
253                                            _OPEN_ID_SREG_ATTR_EMAIL);
254                            }
255                    }
256    
257                    if (authSuccess.hasExtension(AxMessage.OPENID_NS_AX)) {
258                            MessageExtension messageExtension = authSuccess.getExtension(
259                                    AxMessage.OPENID_NS_AX);
260    
261                            if (messageExtension instanceof FetchResponse) {
262                                    FetchResponse fetchResponse = (FetchResponse)messageExtension;
263    
264                                    String openIdProvider = getOpenIdProvider(
265                                            discoveryInformation.getOPEndpoint());
266    
267                                    String[] openIdAXTypes = PropsUtil.getArray(
268                                            PropsKeys.OPEN_ID_AX_SCHEMA, new Filter(openIdProvider));
269    
270                                    for (String openIdAXType : openIdAXTypes) {
271                                            if (openIdAXType.equals(_OPEN_ID_AX_ATTR_EMAIL)) {
272                                                    if (Validator.isNull(emailAddress)) {
273                                                            emailAddress = getFirstValue(
274                                                                    fetchResponse.getAttributeValues(
275                                                                            _OPEN_ID_AX_ATTR_EMAIL));
276                                                    }
277                                            }
278                                            else if (openIdAXType.equals(_OPEN_ID_AX_ATTR_FIRST_NAME)) {
279                                                    if (Validator.isNull(firstName)) {
280                                                            firstName = getFirstValue(
281                                                                    fetchResponse.getAttributeValues(
282                                                                            _OPEN_ID_AX_ATTR_FIRST_NAME));
283                                                    }
284                                            }
285                                            else if (openIdAXType.equals(_OPEN_ID_AX_ATTR_FULL_NAME)) {
286                                                    String fullName = fetchResponse.getAttributeValue(
287                                                            _OPEN_ID_AX_ATTR_FULL_NAME);
288    
289                                                    String[] names = splitFullName(fullName);
290    
291                                                    if (names != null) {
292                                                            if (Validator.isNull(firstName)) {
293                                                                    firstName = names[0];
294                                                            }
295    
296                                                            if (Validator.isNull(lastName)) {
297                                                                    lastName = names[1];
298                                                            }
299                                                    }
300                                            }
301                                            else if (openIdAXType.equals(_OPEN_ID_AX_ATTR_LAST_NAME)) {
302                                                    if (Validator.isNull(lastName)) {
303                                                            lastName = getFirstValue(
304                                                                    fetchResponse.getAttributeValues(
305                                                                            _OPEN_ID_AX_ATTR_LAST_NAME));
306                                                    }
307                                            }
308                                    }
309                            }
310                    }
311    
312                    String openId = OpenIdUtil.normalize(authSuccess.getIdentity());
313    
314                    User user = UserLocalServiceUtil.fetchUserByOpenId(
315                            themeDisplay.getCompanyId(), openId);
316    
317                    if (user != null) {
318                            session.setAttribute(
319                                    WebKeys.OPEN_ID_LOGIN, new Long(user.getUserId()));
320    
321                            return null;
322                    }
323    
324                    if (Validator.isNull(firstName) || Validator.isNull(lastName) ||
325                            Validator.isNull(emailAddress)) {
326    
327                            SessionMessages.add(request, "missingOpenIdUserInformation");
328    
329                            if (_log.isInfoEnabled()) {
330                                    _log.info(
331                                            "The OpenID provider did not send the required " +
332                                                    "attributes to create an account");
333                            }
334    
335                            String createAccountURL = PortalUtil.getCreateAccountURL(
336                                    request, themeDisplay);
337    
338                            createAccountURL = HttpUtil.setParameter(
339                                    createAccountURL, "openId", openId);
340    
341                            session.setAttribute(WebKeys.OPEN_ID_LOGIN_PENDING, Boolean.TRUE);
342    
343                            return createAccountURL;
344                    }
345    
346                    long creatorUserId = 0;
347                    long companyId = themeDisplay.getCompanyId();
348                    boolean autoPassword = false;
349                    String password1 = PwdGenerator.getPassword();
350                    String password2 = password1;
351                    boolean autoScreenName = true;
352                    String screenName = StringPool.BLANK;
353                    long facebookId = 0;
354                    Locale locale = themeDisplay.getLocale();
355                    String middleName = StringPool.BLANK;
356                    int prefixId = 0;
357                    int suffixId = 0;
358                    boolean male = true;
359                    int birthdayMonth = Calendar.JANUARY;
360                    int birthdayDay = 1;
361                    int birthdayYear = 1970;
362                    String jobTitle = StringPool.BLANK;
363                    long[] groupIds = null;
364                    long[] organizationIds = null;
365                    long[] roleIds = null;
366                    long[] userGroupIds = null;
367                    boolean sendEmail = false;
368    
369                    ServiceContext serviceContext = new ServiceContext();
370    
371                    user = UserLocalServiceUtil.addUser(
372                            creatorUserId, companyId, autoPassword, password1, password2,
373                            autoScreenName, screenName, emailAddress, facebookId, openId,
374                            locale, firstName, middleName, lastName, prefixId, suffixId, male,
375                            birthdayMonth, birthdayDay, birthdayYear, jobTitle, groupIds,
376                            organizationIds, roleIds, userGroupIds, sendEmail, serviceContext);
377    
378                    session.setAttribute(WebKeys.OPEN_ID_LOGIN, new Long(user.getUserId()));
379    
380                    return null;
381            }
382    
383            protected void sendOpenIdRequest(
384                            ThemeDisplay themeDisplay, ActionRequest actionRequest,
385                            ActionResponse actionResponse)
386                    throws Exception {
387    
388                    HttpServletRequest request = PortalUtil.getHttpServletRequest(
389                            actionRequest);
390                    HttpServletResponse response = PortalUtil.getHttpServletResponse(
391                            actionResponse);
392                    HttpSession session = request.getSession();
393    
394                    ActionResponseImpl actionResponseImpl =
395                            (ActionResponseImpl)actionResponse;
396    
397                    String openId = ParamUtil.getString(actionRequest, "openId");
398    
399                    PortletURL portletURL = actionResponseImpl.createActionURL();
400    
401                    portletURL.setParameter("saveLastPath", Boolean.FALSE.toString());
402                    portletURL.setParameter(Constants.CMD, Constants.READ);
403                    portletURL.setParameter("struts_action", "/login/open_id");
404    
405                    ConsumerManager manager = OpenIdUtil.getConsumerManager();
406    
407                    List<DiscoveryInformation> discoveries = manager.discover(openId);
408    
409                    DiscoveryInformation discovered = manager.associate(discoveries);
410    
411                    session.setAttribute(WebKeys.OPEN_ID_DISCO, discovered);
412    
413                    AuthRequest authRequest = manager.authenticate(
414                            discovered, portletURL.toString(), themeDisplay.getPortalURL());
415    
416                    if (UserLocalServiceUtil.fetchUserByOpenId(
417                                    themeDisplay.getCompanyId(), openId) != null) {
418    
419                            response.sendRedirect(authRequest.getDestinationUrl(true));
420    
421                            return;
422                    }
423    
424                    String screenName = OpenIdUtil.getScreenName(openId);
425    
426                    User user = UserLocalServiceUtil.fetchUserByScreenName(
427                            themeDisplay.getCompanyId(), screenName);
428    
429                    if (user != null) {
430                            UserLocalServiceUtil.updateOpenId(user.getUserId(), openId);
431    
432                            response.sendRedirect(authRequest.getDestinationUrl(true));
433    
434                            return;
435                    }
436    
437                    FetchRequest fetchRequest = FetchRequest.createFetchRequest();
438    
439                    String openIdProvider = getOpenIdProvider(discovered.getOPEndpoint());
440    
441                    String[] openIdAXTypes = PropsUtil.getArray(
442                            PropsKeys.OPEN_ID_AX_SCHEMA, new Filter(openIdProvider));
443    
444                    for (String openIdAXType : openIdAXTypes) {
445                            fetchRequest.addAttribute(
446                                    openIdAXType,
447                                    PropsUtil.get(
448                                            _OPEN_ID_AX_TYPE.concat(openIdAXType),
449                                            new Filter(openIdProvider)), true);
450                    }
451    
452                    authRequest.addExtension(fetchRequest);
453    
454                    SRegRequest sRegRequest = SRegRequest.createFetchRequest();
455    
456                    sRegRequest.addAttribute(_OPEN_ID_SREG_ATTR_EMAIL, true);
457                    sRegRequest.addAttribute(_OPEN_ID_SREG_ATTR_FULLNAME, true);
458    
459                    authRequest.addExtension(sRegRequest);
460    
461                    response.sendRedirect(authRequest.getDestinationUrl(true));
462            }
463    
464            protected String[] splitFullName(String fullName) {
465                    if (Validator.isNull(fullName)) {
466                            return null;
467                    }
468    
469                    int pos = fullName.indexOf(CharPool.SPACE);
470    
471                    if ((pos != -1) && ((pos + 1) < fullName.length())) {
472                            String[] names = new String[2];
473    
474                            names[0] = fullName.substring(0, pos);
475                            names[1] = fullName.substring(pos + 1);
476    
477                            return names;
478                    }
479    
480                    return null;
481            }
482    
483            private static final boolean _CHECK_METHOD_ON_PROCESS_ACTION = false;
484    
485            private static final String _OPEN_ID_AX_ATTR_EMAIL = "email";
486    
487            private static final String _OPEN_ID_AX_ATTR_FIRST_NAME = "firstname";
488    
489            private static final String _OPEN_ID_AX_ATTR_FULL_NAME = "fullname";
490    
491            private static final String _OPEN_ID_AX_ATTR_LAST_NAME = "lastname";
492    
493            private static final String _OPEN_ID_AX_TYPE = "open.id.ax.type.";
494    
495            private static final String _OPEN_ID_PROVIDER_DEFAULT = "default";
496    
497            private static final String _OPEN_ID_SREG_ATTR_EMAIL = "email";
498    
499            private static final String _OPEN_ID_SREG_ATTR_FULLNAME = "fullname";
500    
501            private static Log _log = LogFactoryUtil.getLog(OpenIdAction.class);
502    
503    }