001    /**
002     * Copyright (c) 2000-present 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.UserEmailAddressException;
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.PwdGenerator;
030    import com.liferay.portal.kernel.util.StringPool;
031    import com.liferay.portal.kernel.util.Validator;
032    import com.liferay.portal.model.User;
033    import com.liferay.portal.security.auth.PrincipalException;
034    import com.liferay.portal.service.ServiceContext;
035    import com.liferay.portal.service.UserLocalServiceUtil;
036    import com.liferay.portal.struts.PortletAction;
037    import com.liferay.portal.theme.ThemeDisplay;
038    import com.liferay.portal.util.OpenIdUtil;
039    import com.liferay.portal.util.PortalUtil;
040    import com.liferay.portal.util.PropsUtil;
041    import com.liferay.portal.util.PropsValues;
042    import com.liferay.portal.util.WebKeys;
043    import com.liferay.portlet.ActionResponseImpl;
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 = themeDisplay.getURLSignIn();
117                                    }
118    
119                                    sendRedirect(actionRequest, actionResponse, redirect);
120                            }
121                            else {
122                                    sendOpenIdRequest(themeDisplay, actionRequest, actionResponse);
123                            }
124                    }
125                    catch (Exception e) {
126                            if (e instanceof OpenIDException) {
127                                    if (_log.isInfoEnabled()) {
128                                            _log.info(
129                                                    "Error communicating with OpenID provider: " +
130                                                            e.getMessage());
131                                    }
132    
133                                    SessionErrors.add(actionRequest, e.getClass());
134                            }
135                            else if (e instanceof
136                                                    UserEmailAddressException.MustNotBeDuplicate) {
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    
203                    request = PortalUtil.getOriginalServletRequest(request);
204    
205                    HttpSession session = request.getSession();
206    
207                    ConsumerManager consumerManager = OpenIdUtil.getConsumerManager();
208    
209                    ParameterList parameterList = new ParameterList(
210                            request.getParameterMap());
211    
212                    DiscoveryInformation discoveryInformation =
213                            (DiscoveryInformation)session.getAttribute(WebKeys.OPEN_ID_DISCO);
214    
215                    if (discoveryInformation == null) {
216                            return null;
217                    }
218    
219                    String receivingURL = ParamUtil.getString(request, "openid.return_to");
220    
221                    VerificationResult verificationResult = consumerManager.verify(
222                            receivingURL, parameterList, discoveryInformation);
223    
224                    Identifier identifier = verificationResult.getVerifiedId();
225    
226                    if (identifier == null) {
227                            return null;
228                    }
229    
230                    AuthSuccess authSuccess =
231                            (AuthSuccess)verificationResult.getAuthResponse();
232    
233                    String firstName = null;
234                    String lastName = null;
235                    String emailAddress = null;
236    
237                    if (authSuccess.hasExtension(SRegMessage.OPENID_NS_SREG)) {
238                            MessageExtension messageExtension = authSuccess.getExtension(
239                                    SRegMessage.OPENID_NS_SREG);
240    
241                            if (messageExtension instanceof SRegResponse) {
242                                    SRegResponse sregResp = (SRegResponse)messageExtension;
243    
244                                    String fullName = GetterUtil.getString(
245                                            sregResp.getAttributeValue(_OPEN_ID_SREG_ATTR_FULLNAME));
246    
247                                    String[] names = splitFullName(fullName);
248    
249                                    if (names != null) {
250                                            firstName = names[0];
251                                            lastName = names[1];
252                                    }
253    
254                                    emailAddress = sregResp.getAttributeValue(
255                                            _OPEN_ID_SREG_ATTR_EMAIL);
256                            }
257                    }
258    
259                    if (authSuccess.hasExtension(AxMessage.OPENID_NS_AX)) {
260                            MessageExtension messageExtension = authSuccess.getExtension(
261                                    AxMessage.OPENID_NS_AX);
262    
263                            if (messageExtension instanceof FetchResponse) {
264                                    FetchResponse fetchResponse = (FetchResponse)messageExtension;
265    
266                                    String openIdProvider = getOpenIdProvider(
267                                            discoveryInformation.getOPEndpoint());
268    
269                                    String[] openIdAXTypes = PropsUtil.getArray(
270                                            PropsKeys.OPEN_ID_AX_SCHEMA, new Filter(openIdProvider));
271    
272                                    for (String openIdAXType : openIdAXTypes) {
273                                            if (openIdAXType.equals(_OPEN_ID_AX_ATTR_EMAIL)) {
274                                                    if (Validator.isNull(emailAddress)) {
275                                                            emailAddress = getFirstValue(
276                                                                    fetchResponse.getAttributeValues(
277                                                                            _OPEN_ID_AX_ATTR_EMAIL));
278                                                    }
279                                            }
280                                            else if (openIdAXType.equals(_OPEN_ID_AX_ATTR_FIRST_NAME)) {
281                                                    if (Validator.isNull(firstName)) {
282                                                            firstName = getFirstValue(
283                                                                    fetchResponse.getAttributeValues(
284                                                                            _OPEN_ID_AX_ATTR_FIRST_NAME));
285                                                    }
286                                            }
287                                            else if (openIdAXType.equals(_OPEN_ID_AX_ATTR_FULL_NAME)) {
288                                                    String fullName = fetchResponse.getAttributeValue(
289                                                            _OPEN_ID_AX_ATTR_FULL_NAME);
290    
291                                                    String[] names = splitFullName(fullName);
292    
293                                                    if (names != null) {
294                                                            if (Validator.isNull(firstName)) {
295                                                                    firstName = names[0];
296                                                            }
297    
298                                                            if (Validator.isNull(lastName)) {
299                                                                    lastName = names[1];
300                                                            }
301                                                    }
302                                            }
303                                            else if (openIdAXType.equals(_OPEN_ID_AX_ATTR_LAST_NAME)) {
304                                                    if (Validator.isNull(lastName)) {
305                                                            lastName = getFirstValue(
306                                                                    fetchResponse.getAttributeValues(
307                                                                            _OPEN_ID_AX_ATTR_LAST_NAME));
308                                                    }
309                                            }
310                                    }
311                            }
312                    }
313    
314                    String openId = OpenIdUtil.normalize(authSuccess.getIdentity());
315    
316                    User user = UserLocalServiceUtil.fetchUserByOpenId(
317                            themeDisplay.getCompanyId(), openId);
318    
319                    if (user != null) {
320                            session.setAttribute(
321                                    WebKeys.OPEN_ID_LOGIN, new Long(user.getUserId()));
322    
323                            return null;
324                    }
325    
326                    if (Validator.isNull(firstName) || Validator.isNull(lastName) ||
327                            Validator.isNull(emailAddress)) {
328    
329                            SessionMessages.add(request, "openIdUserInformationMissing");
330    
331                            if (_log.isInfoEnabled()) {
332                                    _log.info(
333                                            "The OpenID provider did not send the required " +
334                                                    "attributes to create an account");
335                            }
336    
337                            String createAccountURL = PortalUtil.getCreateAccountURL(
338                                    request, themeDisplay);
339    
340                            createAccountURL = HttpUtil.setParameter(
341                                    createAccountURL, "openId", openId);
342    
343                            session.setAttribute(WebKeys.OPEN_ID_LOGIN_PENDING, Boolean.TRUE);
344    
345                            return createAccountURL;
346                    }
347    
348                    long creatorUserId = 0;
349                    long companyId = themeDisplay.getCompanyId();
350                    boolean autoPassword = false;
351                    String password1 = PwdGenerator.getPassword();
352                    String password2 = password1;
353                    boolean autoScreenName = true;
354                    String screenName = StringPool.BLANK;
355                    long facebookId = 0;
356                    Locale locale = themeDisplay.getLocale();
357                    String middleName = StringPool.BLANK;
358                    int prefixId = 0;
359                    int suffixId = 0;
360                    boolean male = true;
361                    int birthdayMonth = Calendar.JANUARY;
362                    int birthdayDay = 1;
363                    int birthdayYear = 1970;
364                    String jobTitle = StringPool.BLANK;
365                    long[] groupIds = null;
366                    long[] organizationIds = null;
367                    long[] roleIds = null;
368                    long[] userGroupIds = null;
369                    boolean sendEmail = false;
370    
371                    ServiceContext serviceContext = new ServiceContext();
372    
373                    user = UserLocalServiceUtil.addUser(
374                            creatorUserId, companyId, autoPassword, password1, password2,
375                            autoScreenName, screenName, emailAddress, facebookId, openId,
376                            locale, firstName, middleName, lastName, prefixId, suffixId, male,
377                            birthdayMonth, birthdayDay, birthdayYear, jobTitle, groupIds,
378                            organizationIds, roleIds, userGroupIds, sendEmail, serviceContext);
379    
380                    session.setAttribute(WebKeys.OPEN_ID_LOGIN, new Long(user.getUserId()));
381    
382                    return null;
383            }
384    
385            protected void sendOpenIdRequest(
386                            ThemeDisplay themeDisplay, ActionRequest actionRequest,
387                            ActionResponse actionResponse)
388                    throws Exception {
389    
390                    HttpServletRequest request = PortalUtil.getHttpServletRequest(
391                            actionRequest);
392                    HttpServletResponse response = PortalUtil.getHttpServletResponse(
393                            actionResponse);
394                    HttpSession session = request.getSession();
395    
396                    ActionResponseImpl actionResponseImpl =
397                            (ActionResponseImpl)actionResponse;
398    
399                    String openId = ParamUtil.getString(actionRequest, "openId");
400    
401                    PortletURL portletURL = actionResponseImpl.createActionURL();
402    
403                    portletURL.setParameter("saveLastPath", Boolean.FALSE.toString());
404                    portletURL.setParameter(Constants.CMD, Constants.READ);
405                    portletURL.setParameter("struts_action", "/login/open_id");
406    
407                    ConsumerManager manager = OpenIdUtil.getConsumerManager();
408    
409                    List<DiscoveryInformation> discoveries = manager.discover(openId);
410    
411                    DiscoveryInformation discovered = manager.associate(discoveries);
412    
413                    session.setAttribute(WebKeys.OPEN_ID_DISCO, discovered);
414    
415                    AuthRequest authRequest = manager.authenticate(
416                            discovered, portletURL.toString(), themeDisplay.getPortalURL());
417    
418                    if (UserLocalServiceUtil.fetchUserByOpenId(
419                                    themeDisplay.getCompanyId(), openId) != null) {
420    
421                            response.sendRedirect(authRequest.getDestinationUrl(true));
422    
423                            return;
424                    }
425    
426                    String screenName = OpenIdUtil.getScreenName(openId);
427    
428                    User user = UserLocalServiceUtil.fetchUserByScreenName(
429                            themeDisplay.getCompanyId(), screenName);
430    
431                    if (user != null) {
432                            UserLocalServiceUtil.updateOpenId(user.getUserId(), openId);
433    
434                            response.sendRedirect(authRequest.getDestinationUrl(true));
435    
436                            return;
437                    }
438    
439                    FetchRequest fetchRequest = FetchRequest.createFetchRequest();
440    
441                    String openIdProvider = getOpenIdProvider(discovered.getOPEndpoint());
442    
443                    String[] openIdAXTypes = PropsUtil.getArray(
444                            PropsKeys.OPEN_ID_AX_SCHEMA, new Filter(openIdProvider));
445    
446                    for (String openIdAXType : openIdAXTypes) {
447                            fetchRequest.addAttribute(
448                                    openIdAXType,
449                                    PropsUtil.get(
450                                            _OPEN_ID_AX_TYPE.concat(openIdAXType),
451                                            new Filter(openIdProvider)),
452                                    true);
453                    }
454    
455                    authRequest.addExtension(fetchRequest);
456    
457                    SRegRequest sRegRequest = SRegRequest.createFetchRequest();
458    
459                    sRegRequest.addAttribute(_OPEN_ID_SREG_ATTR_EMAIL, true);
460                    sRegRequest.addAttribute(_OPEN_ID_SREG_ATTR_FULLNAME, true);
461    
462                    authRequest.addExtension(sRegRequest);
463    
464                    response.sendRedirect(authRequest.getDestinationUrl(true));
465            }
466    
467            protected String[] splitFullName(String fullName) {
468                    if (Validator.isNull(fullName)) {
469                            return null;
470                    }
471    
472                    int pos = fullName.indexOf(CharPool.SPACE);
473    
474                    if ((pos != -1) && ((pos + 1) < fullName.length())) {
475                            String[] names = new String[2];
476    
477                            names[0] = fullName.substring(0, pos);
478                            names[1] = fullName.substring(pos + 1);
479    
480                            return names;
481                    }
482    
483                    return null;
484            }
485    
486            private static final boolean _CHECK_METHOD_ON_PROCESS_ACTION = false;
487    
488            private static final String _OPEN_ID_AX_ATTR_EMAIL = "email";
489    
490            private static final String _OPEN_ID_AX_ATTR_FIRST_NAME = "firstname";
491    
492            private static final String _OPEN_ID_AX_ATTR_FULL_NAME = "fullname";
493    
494            private static final String _OPEN_ID_AX_ATTR_LAST_NAME = "lastname";
495    
496            private static final String _OPEN_ID_AX_TYPE = "open.id.ax.type.";
497    
498            private static final String _OPEN_ID_PROVIDER_DEFAULT = "default";
499    
500            private static final String _OPEN_ID_SREG_ATTR_EMAIL = "email";
501    
502            private static final String _OPEN_ID_SREG_ATTR_FULLNAME = "fullname";
503    
504            private static final Log _log = LogFactoryUtil.getLog(OpenIdAction.class);
505    
506    }