Index: core/xwiki-core/pom.xml =================================================================== --- core/xwiki-core/pom.xml (revision 11567) +++ core/xwiki-core/pom.xml (working copy) @@ -325,6 +325,13 @@ 2.0 + + + org.openid4java + openid4java + 0.9.3 + + dom4j Index: core/xwiki-core/src/main/java/com/xpn/xwiki/user/impl/openid/OpenIDHelper.java =================================================================== --- core/xwiki-core/src/main/java/com/xpn/xwiki/user/impl/openid/OpenIDHelper.java (revision 0) +++ core/xwiki-core/src/main/java/com/xpn/xwiki/user/impl/openid/OpenIDHelper.java (revision 0) @@ -0,0 +1,272 @@ +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ +package com.xpn.xwiki.user.impl.openid; + +import java.util.HashMap; +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.openid4java.consumer.ConsumerException; +import org.openid4java.consumer.ConsumerManager; + +import com.xpn.xwiki.XWiki; +import com.xpn.xwiki.XWikiContext; +import com.xpn.xwiki.XWikiException; +import com.xpn.xwiki.doc.XWikiDocument; +import com.xpn.xwiki.objects.BaseObject; +import com.xpn.xwiki.objects.classes.BaseClass; +import com.xpn.xwiki.store.query.Query; +import com.xpn.xwiki.store.query.QueryManager; + +/** + * OpenID helper class (singleton) + * + * @author Markus Lanthaler + */ +public class OpenIDHelper +{ + private static final Log log = LogFactory.getLog(OpenIDHelper.class); + + private static OpenIDHelper instance; + + private ConsumerManager manager; + + /** + * The constructer instantiates a ConsumerManager object. + * + * @throws ConsumerException + */ + private OpenIDHelper() throws ConsumerException + { + manager = new ConsumerManager(); + } + + /** + * @return the unique ConsumerManager instance + * @throws ConsumerException + */ + public static ConsumerManager getConsumerManager() throws ConsumerException + { + if (instance == null) + instance = new OpenIDHelper(); + + return instance.manager; + } + + /** + * Converts an OpenID identifier to an user name which can be used as a XWiki document name. This method + * doesn't check if that document already exists! + * + * @param openid_identifier the OpenID identifier to convert + * @param context + * @return returns the converted OpenID identifier + */ + public static String openIdIdentifierToUsername(String openid_identifier, XWikiContext context) + { + return "OpenID-" + context.getWiki().clearName(openid_identifier, true, true, context) + "-" + + openid_identifier.hashCode(); + } + + /** + * Find the user (the full document name) belonging to a specific OpenID identifier. + * + * @param openid_identifier the OpenID identifier to search + * @param context + * @return the full document name for the user belonging to the OpenID identifier or NULL if the OpenID identifier + * was not found. + */ + public static String findUser(String openid_identifier, XWikiContext context) throws XWikiException + { + XWiki xwiki = context.getWiki(); + + QueryManager qm = xwiki.getStore().getQueryManager(); + Query search_user = qm.getNamedQuery("getUserDocByOpenIdIdentifier"); + + if (search_user == null) + throw new RuntimeException("Named query 'getUserDocByOpenIdIdentifier' was not found!"); + + search_user.bindValue("identifier", openid_identifier); + + List found_users = search_user.setLimit(1).execute(); + if (found_users.size() > 0) { + if (log.isDebugEnabled()) { + log.debug("OpenID " + openid_identifier + " already registered."); + } + return found_users.get(0); + } + + return null; + } + + /** + * This method creates a new OpenID user with a random password. The user doesn't need to know it - in fact he even + * doesn't know of its existence. It's just used so that the {@link PersistentLoginManager} and the Authenticator + * can be used as implemented at the moment. + * + * @param openid_identifier OpenID identifier + * @param firstname users first name + * @param lastname users last name + * @param email users email address + * @param context + * @return a code which describes the success or failure of the method. + * @throws XWikiException + */ + public static int createUser(String openid_identifier, String firstname, String lastname, String email, + XWikiContext context) throws XWikiException + { + XWiki xwiki = context.getWiki(); + String xwikiname = openIdIdentifierToUsername(openid_identifier, context); + + // Generate a unique document name for the new user + XWikiDocument userdoc = xwiki.getDocument("XWiki." + xwikiname, context); + while (userdoc.isNew() == false) { + userdoc = xwiki.getDocument("XWiki." + xwikiname + "-" + xwiki.generateRandomString(5), context); + } + + if (userdoc.isNew()) { + if (log.isDebugEnabled()) { + log.debug("Creating user for OpenID " + openid_identifier); + } + + String content = "#includeForm(\"XWiki.XWikiUserSheet\")"; + String parent = "XWiki.XWikiUsers"; + HashMap map = new HashMap(); + map.put("active", "1"); + if (firstname == null || firstname.trim().isEmpty()) + map.put("first_name", openid_identifier); + else + map.put("first_name", firstname); + + if (lastname != null) + map.put("last_name", lastname); + + if (email != null) + map.put("email", email); + + String password = xwiki.generateRandomString(255); + map.put("password", password); + + int result; + if ((result = xwiki.createUser(xwikiname, map, parent, content, "edit", context)) == 1) { + userdoc = xwiki.getDocument("XWiki." + xwikiname, context); + + if (attachOpenIdToUser(userdoc, openid_identifier, context) == false) { + if (log.isDebugEnabled()) { + log.debug("Deleting previously created document for OpenID user " + openid_identifier + + ". OpenID identifier is already in use."); + } + xwiki.deleteDocument(userdoc, false, context); + result = -3; + } + } + return result; + } else { + if (log.isDebugEnabled()) { + log.debug("User page already exists for OpenID " + openid_identifier); + } + return -3; + } + } + + /** + * Attach an OpenID identifier to a document. The method assures that the identifier is unique. If it is already + * used, the method fails and returns FALSE. The document is automatically saved by this method. + * + * @param doc document to which the OpenID identifier should be attached + * @param openid_identifier the OpenID identifier to attach + * @param context + * @return TRUE if attaching the OpenID identifier was successful, otherwise false. + * @throws XWikiException + */ + public static synchronized boolean attachOpenIdToUser(XWikiDocument doc, String openid_identifier, + XWikiContext context) throws XWikiException + { + XWiki xwiki = context.getWiki(); + + if (findUser(openid_identifier, context) != null) { + if (log.isDebugEnabled()) { + log.debug("OpenID " + openid_identifier + " is already registered"); + } + return false; + } + + BaseClass baseclass = getOpenIdIdentifierClass(context); + BaseObject newobject = (BaseObject) baseclass.newObject(context); + newobject.setName(doc.getFullName()); + newobject.setStringValue("identifier", openid_identifier); + + doc.addObject(baseclass.getName(), newobject); + xwiki.saveDocument(doc, context.getMessageTool().get("core.comment.addedOpenIdIdentifier"), context); + + return true; + } + + /** + * Since for all OpenID users a random password which the user doesn't know is created, we need some way to retrieve + * it. This method does exactly that. + * + * @param openid_identifier + * @param context + * @return the internal used password for the OpenID user or NULL if the user was not found. + */ + public static String getOpenIdUserPassword(String openid_identifier, XWikiContext context) throws XWikiException + { + String xwikiname = findUser(openid_identifier, context); + + XWikiDocument doc = context.getWiki().getDocument(xwikiname, context); + // We only allow empty password from users having a XWikiUsers object. + if (doc.getObject("XWiki.OpenIdIdentifier") != null && doc.getObject("XWiki.XWikiUsers") != null) { + return doc.getStringValue("XWiki.XWikiUsers", "password"); + } + + return null; + } + + /** + * Verify if the XWiki.OpenIdIdentifier page exists and that it contains all the required + * configuration properties to make the OpenID feature work properly. If some properties are missing they are + * created and saved in the database. + * + * @param context the XWiki Context + * @return the OpenIDIdentifier Base Class object containing the properties + * @throws XWikiException if an error happens while saving + */ + public static BaseClass getOpenIdIdentifierClass(XWikiContext context) throws XWikiException + { + XWiki xwiki = context.getWiki(); + XWikiDocument doc; + boolean needs_update = false; + + doc = xwiki.getDocument("XWiki.OpenIdIdentifier", context); + + BaseClass bclass = doc.getxWikiClass(); + bclass.setName("XWiki.OpenIdIdentifier"); + + needs_update |= bclass.addTextField("identifier", "Identifier", 2048); + needs_update |= bclass.addTextField("password", "Password", 255); + + if (needs_update) + xwiki.saveDocument(doc, context); + + return bclass; + } +} Index: core/xwiki-core/src/main/java/com/xpn/xwiki/user/impl/xwiki/MyFormAuthenticator.java =================================================================== --- core/xwiki-core/src/main/java/com/xpn/xwiki/user/impl/xwiki/MyFormAuthenticator.java (revision 11567) +++ core/xwiki-core/src/main/java/com/xpn/xwiki/user/impl/xwiki/MyFormAuthenticator.java (working copy) @@ -23,6 +23,7 @@ import java.io.IOException; import java.security.Principal; +import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -30,6 +31,14 @@ import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + +import org.openid4java.consumer.ConsumerManager; +import org.openid4java.consumer.VerificationResult; +import org.openid4java.discovery.Identifier; +import org.openid4java.discovery.DiscoveryInformation; +import org.openid4java.message.*; +import org.openid4java.OpenIDException; + import org.securityfilter.authenticator.Authenticator; import org.securityfilter.authenticator.FormAuthenticator; import org.securityfilter.filter.SecurityRequestWrapper; @@ -37,6 +46,7 @@ import com.xpn.xwiki.XWikiContext; import com.xpn.xwiki.XWikiException; +import com.xpn.xwiki.user.impl.openid.OpenIDHelper; import com.xpn.xwiki.web.SavedRequestRestorerFilter; public class MyFormAuthenticator extends FormAuthenticator implements Authenticator, XWikiAuthenticator @@ -116,7 +126,7 @@ } } catch (Exception e) { // in case of exception we continue on Form Auth. - // we don't want this to interfere with the most common behavior + // we don't want this to interfere with the most common behaviour } // process any persistent login information, if user is not already logged in, @@ -139,13 +149,27 @@ } } - // process login form submittal + // process login form data if ((this.loginSubmitPattern != null) && request.getMatchableURL().endsWith(this.loginSubmitPattern)) { - String username = convertUsername(request.getParameter(FORM_USERNAME), context); - String password = request.getParameter(FORM_PASSWORD); - String rememberme = request.getParameter(FORM_REMEMBERME); - rememberme = (rememberme == null) ? "false" : rememberme; - return processLogin(username, password, rememberme, request, response, context); + if (request.getParameter("authentication_method") != null + && request.getParameter("authentication_method").equalsIgnoreCase("openid")) { + // OpenID login + String openid_identifier = request.getParameter("openid_identifier"); + String rememberme = request.getParameter(FORM_REMEMBERME); + rememberme = (rememberme == null) ? "false" : rememberme; + return processOpenIdLogin(openid_identifier, rememberme, request, response, context); + } else if (request.getParameter("authentication_method") != null + && request.getParameter("authentication_method").equalsIgnoreCase("useraccount")) { + // Normal user account login + String username = convertUsername(request.getParameter(FORM_USERNAME), context); + String password = request.getParameter(FORM_PASSWORD); + String rememberme = request.getParameter(FORM_REMEMBERME); + rememberme = (rememberme == null) ? "false" : rememberme; + return processLogin(username, password, rememberme, request, response, context); + } else if (request.getParameter("openid.mode") != null) { + // OpenID: OP response + return processOpenIdLoginResponse(request, response, context); + } } return false; } @@ -155,6 +179,9 @@ * abort further processing after the method completes (for example, if a redirect was sent as part of the login * processing). * + * @param username + * @param password + * @param rememberme * @param request * @param response * @return true if the filter should return after this method ends, false otherwise @@ -169,7 +196,8 @@ log.info("User " + principal.getName() + " has been logged-in"); } - // invalidate old session if the user was already authenticated, and they logged in as a different user + // invalidate old session if the user was already authenticated, and they logged in as a + // different user if (request.getUserPrincipal() != null && !username.equals(request.getRemoteUser())) { request.getSession().invalidate(); } @@ -190,7 +218,8 @@ Boolean bAjax = (Boolean) context.get("ajax"); if ((bAjax == null) || (!bAjax.booleanValue())) { String continueToURL = getContinueToURL(request); - // This is the url that the user was initially accessing before being prompted for login. + // This is the url that the user was initially accessing before being prompted for + // login. response.sendRedirect(response.encodeRedirectURL(continueToURL)); } } else { @@ -215,6 +244,158 @@ } /** + * Process an OpenID login. Returns true if SecurityFilter should abort further processing after the method + * completes (for example, if a redirect was sent as part of the login processing which is the normal behaviour). + * + * @param openid_identifier + * @param rememberme + * @param request + * @param response + * @return true if the filter should return after this method ends, false otherwise + */ + public boolean processOpenIdLogin(String openid_identifier, String rememberme, SecurityRequestWrapper request, + HttpServletResponse response, XWikiContext context) throws Exception + { + if (openid_identifier.trim().equals("")) { + context.put("message", "noopenid"); + return false; + } + + try { + ConsumerManager manager = OpenIDHelper.getConsumerManager(); + + String return_to_url = + context.getWiki().getExternalURL("XWikiLogin", "loginsubmit", "rememberme=" + rememberme, context); + + List discoveries = manager.discover(openid_identifier); + DiscoveryInformation discovered = manager.associate(discoveries); + + // store the discovery information in the user's session + request.getSession().setAttribute("openid-discovery", discovered); + + AuthRequest auth_request = manager.authenticate(discovered, return_to_url); + + // FIXME Fix this! + auth_request.setRealm("http://localhost:8181/xwiki/"); + + if (log.isInfoEnabled()) { + log.info("Redirecting user to OP (OpenID identifier: " + openid_identifier + ")"); + } + + if (discovered.isVersion2()) { + // OpenID 2.0 supports HTML FORM Redirection which allows payloads >2048 bytes + + // TODO Fix this to really generate a HTML form! + response.sendRedirect(auth_request.getDestinationUrl(true)); + + // VelocityContext vcontext = (VelocityContext) context.get("vcontext"); + // vcontext.put("op_endpoint", auth_request.getDestinationUrl(false)); + // vcontext.put("openid_parameters", auth_request.getParameterMap()); + // response.sendRedirect(response.encodeRedirectURL(request.getContextPath() + "/openidformredirect")); + } else { + // The only method supported in OpenID 1.x is a HTTP-redirect (GET) to the OpenID Provider endpoint (the + // redirect-URL usually limited ~2048 bytes) + response.sendRedirect(auth_request.getDestinationUrl(true)); + } + } catch (OpenIDException e) { + if (log.isInfoEnabled()) { + log.info("OpenID discovery failed: " + e.getMessage()); + } + + // present error to the user + context.put("message", "loginfailed"); + } + + return true; + } + + /** + * Process the response of an OpenID provider. + * + * @param request + * @param response + * @param context + * @return true if the filter should return after this method ends, false otherwise + */ + public boolean processOpenIdLoginResponse(SecurityRequestWrapper request, HttpServletResponse response, + XWikiContext context) throws Exception + { + try { + ConsumerManager manager = OpenIDHelper.getConsumerManager(); + + // extract the parameters from the authentication response (which come in as a HTTP request from the OpenID + // provider) + ParameterList openid_response = new ParameterList(request.getParameterMap()); + + // retrieve the previously stored discovery information + DiscoveryInformation discovered = + (DiscoveryInformation) request.getSession().getAttribute("openid-discovery"); + + StringBuffer receivingURL = request.getRequestURL(); + String queryString = request.getQueryString(); + if (queryString != null && queryString.length() > 0) + receivingURL.append("?").append(request.getQueryString()); + + VerificationResult verification = manager.verify(receivingURL.toString(), openid_response, discovered); + Identifier verified = verification.getVerifiedId(); + + if (verified != null) { + String username = OpenIDHelper.findUser(verified.getIdentifier(), context); + + if (username == null) { + // No user was found for this OpenID identifier + if (log.isInfoEnabled()) { + log.info("No user for OpenID " + verified.getIdentifier() + " found."); + } + + context.put("message", "openid_not_associated"); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + return true; + } + + String password = OpenIDHelper.getOpenIdUserPassword(verified.getIdentifier(), context); + + Principal principal = authenticate(username, password, context); + if (principal != null) { + // invalidate old session if the user was already authenticated, and they logged in as a different + // user + if (request.getUserPrincipal() != null) { + request.getSession().invalidate(); + } + + // manage persistent login info, if persistent login management is enabled + String rememberme = request.getParameter(FORM_REMEMBERME); + rememberme = (rememberme == null) ? "false" : rememberme; + + if (this.persistentLoginManager != null) { + this.persistentLoginManager.rememberLogin(request, response, username, password); + } + + request.setUserPrincipal(principal); + + String continueToURL = getContinueToURL(request); + response.sendRedirect(response.encodeRedirectURL(continueToURL)); + } + } else { + if (openid_response.getParameter("openid.mode") != null + && openid_response.getParameter("openid.mode").getValue().equals("cancel")) { + context.put("message", "openidlogin_cancelled"); + } else { + if (log.isInfoEnabled() && openid_response.getParameter("error") != null) { + log.info("OpenID login failed (error: " + + openid_response.getParameter("openid.error").getValue() + ")"); + } + context.put("message", "loginfailed"); + } + } + } catch (OpenIDException e) { + context.put("message", "loginfailed"); + } + + return true; + } + + /** * FormAuthenticator has a special case where the user should be sent to a default page if the user spontaneously * submits a login request. * Index: core/xwiki-core/src/main/java/com/xpn/xwiki/user/impl/xwiki/XWikiAuthServiceImpl.java =================================================================== --- core/xwiki-core/src/main/java/com/xpn/xwiki/user/impl/xwiki/XWikiAuthServiceImpl.java (revision 11567) +++ core/xwiki-core/src/main/java/com/xpn/xwiki/user/impl/xwiki/XWikiAuthServiceImpl.java (working copy) @@ -242,7 +242,7 @@ String sql = "delete from XWikiLock as lock where lock.userName=:userName"; Query query = session.createQuery(sql); - String localName = user.getName().substring(user.getName().indexOf(":") + 1); + String localName = user.getName().substring(user.getName().indexOf(":") + 1); query.setString("userName", localName); query.executeUpdate(); } catch (Exception e) { @@ -475,18 +475,23 @@ return user; } - protected boolean checkPassword(String username, String password, XWikiContext context) - throws XWikiException + protected boolean checkPassword(String username, String password, XWikiContext context) throws XWikiException { try { boolean result = false; XWikiDocument doc = context.getWiki().getDocument(username, context); - // We only allow empty password from users having a XWikiUsers object. - if (doc.getObject("XWiki.XWikiUsers") != null) { + if (doc.getObject("XWiki.OpenIdIdentifier") != null) { + // For users having an OpenID the password doesn't need to be adjusted because it is set to the current + // value during the login process String passwd = doc.getStringValue("XWiki.XWikiUsers", "password"); + result = (password.equals(passwd)); + } + if (result == false && doc.getObject("XWiki.XWikiUsers") != null) { + // We only allow empty password from users having a XWikiUsers object. + String passwd = doc.getStringValue("XWiki.XWikiUsers", "password"); password = - ((PasswordClass) context.getWiki().getClass("XWiki.XWikiUsers", context) - .getField("password")).getEquivalentPassword(passwd, password); + ((PasswordClass) context.getWiki().getClass("XWiki.XWikiUsers", context).getField("password")) + .getEquivalentPassword(passwd, password); result = (password.equals(passwd)); } Index: core/xwiki-core/src/main/java/com/xpn/xwiki/web/RegisterAction.java =================================================================== --- core/xwiki-core/src/main/java/com/xpn/xwiki/web/RegisterAction.java (revision 11567) +++ core/xwiki-core/src/main/java/com/xpn/xwiki/web/RegisterAction.java (working copy) @@ -20,41 +20,263 @@ */ package com.xpn.xwiki.web; +import java.util.List; + import com.xpn.xwiki.XWiki; import com.xpn.xwiki.XWikiContext; import com.xpn.xwiki.XWikiException; import com.xpn.xwiki.doc.XWikiDocument; +import com.xpn.xwiki.plugin.ldap.LDAPPlugin; +import com.xpn.xwiki.user.impl.openid.OpenIDHelper; +import com.xpn.xwiki.user.impl.xwiki.MyFormAuthenticator; +import com.xpn.xwiki.user.impl.xwiki.XWikiAuthServiceImpl; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.apache.velocity.VelocityContext; -public class RegisterAction extends XWikiAction { - public boolean action(XWikiContext context) throws XWikiException { +import org.openid4java.consumer.ConsumerManager; +import org.openid4java.consumer.VerificationResult; +import org.openid4java.discovery.Identifier; +import org.openid4java.discovery.DiscoveryInformation; +import org.openid4java.message.ax.AxMessage; +import org.openid4java.message.ax.FetchRequest; +import org.openid4java.message.ax.FetchResponse; +import org.openid4java.message.sreg.SRegMessage; +import org.openid4java.message.sreg.SRegRequest; +import org.openid4java.message.sreg.SRegResponse; +import org.openid4java.message.*; +import org.openid4java.OpenIDException; + +public class RegisterAction extends XWikiAction +{ + private static final Log log = LogFactory.getLog(RegisterAction.class); + + private String template = "register"; + + public boolean action(XWikiContext context) throws XWikiException + { XWiki xwiki = context.getWiki(); XWikiRequest request = context.getRequest(); XWikiResponse response = context.getResponse(); XWikiDocument doc = context.getDoc(); + template = "register"; + String register = request.getParameter("register"); - if ((register!=null)&&(register.equals("1"))) { + if ((register != null) && (register.equals("1"))) { int useemail = xwiki.getXWikiPreferenceAsInt("use_email_verification", 0, context); int result; - if (useemail==1) - result = xwiki.createUser(true, "edit", context); + if (useemail == 1) + result = xwiki.createUser(true, "edit", context); else - result = xwiki.createUser(context); + result = xwiki.createUser(context); VelocityContext vcontext = (VelocityContext) context.get("vcontext"); vcontext.put("reg", new Integer(result)); + } else if ((register != null) && (register.equals("openid-discover"))) { + if (discoverOpenID(context)) + return false; + } else if ((register != null) && (register.equals("openid-confirm"))) { + String confirm = request.getParameter("register-confirm"); + if ("1".equals(confirm) == false) { + confirmOpenIDRegistration(context); + return true; + } else { + VelocityContext vcontext = (VelocityContext) context.get("vcontext"); + vcontext.put("reg", new Integer(registerOpenID(context))); + } } String redirect = Utils.getRedirect(request, null); - if (redirect==null) + if (redirect == null) return true; else { sendRedirect(response, redirect); return false; } - } - - public String render(XWikiContext context) throws XWikiException { - return "register"; - } + } + + public String render(XWikiContext context) throws XWikiException + { + return template; + } + + /** + * Proceed with registration using an OpenID identifier + * + * @param context + * @return returns true if a redirect was sent, otherwise false. + * @author Markus Lanthaler + */ + protected boolean discoverOpenID(XWikiContext context) + { + XWikiRequest request = context.getRequest(); + XWikiResponse response = context.getResponse(); + VelocityContext vcontext = (VelocityContext) context.get("vcontext"); + + // Check for empty OpenID identifier + String openid_identifier = request.getParameter("openid_identifier"); + if (openid_identifier.equals("")) { + // FIXME Change this constant + vcontext.put("reg", new Integer(-4)); + return false; + } + + try { + ConsumerManager manager = OpenIDHelper.getConsumerManager(); + + // configure the return_to URL where your application will receive + // the authentication responses from the OpenID provider + + // String continueToURL = getContinueToURL(request); + // String returnToUrl = response.encodeRedirectURL(continueToURL); + String return_to_url = + context.getWiki().getExternalURL("Register", "register", "register=openid-confirm", context); + + // perform discovery on the user-supplied identifier + List discoveries = manager.discover(openid_identifier); + + // attempt to associate with the OpenID provider + // and retrieve one service endpoint for authentication + DiscoveryInformation discovered = manager.associate(discoveries); + + // store the discovery information in the user's session + request.getSession().setAttribute("openid-disc", discovered); + + // obtain a AuthRequest message to be sent to the OpenID provider + AuthRequest auth_request = manager.authenticate(discovered, return_to_url); + + // FIXME Fix this! + auth_request.setRealm("http://localhost:8181/xwiki/"); + + // Attribute Exchange + FetchRequest att_exchange = FetchRequest.createFetchRequest(); + att_exchange.addAttribute("email", "http://schema.openid.net/contact/email", true); + att_exchange.addAttribute("firstname", "http://axschema.org/namePerson/first", true); + att_exchange.addAttribute("lastname", "http://axschema.org/namePerson/last", true); + + SRegRequest simple_reg_req = SRegRequest.createFetchRequest(); + simple_reg_req.addAttribute("fullname", true); + simple_reg_req.addAttribute("firstname", true); + simple_reg_req.addAttribute("lastname", true); + simple_reg_req.addAttribute("nickname", true); + simple_reg_req.addAttribute("email", true); + + // attach the extension to the authentication request + auth_request.addExtension(att_exchange); + auth_request.addExtension(simple_reg_req); + + if (!discovered.isVersion2()) { + // The only method supported in OpenID 1.x is a HTTP-redirect (GET) to the OpenID Provider endpoint (the + // redirect-URL usually limited ~2048 bytes) + sendRedirect(response, auth_request.getDestinationUrl(true)); + return true; + } else { + // OpenID 2.0 supports HTML FORM Redirection which allows payloads >2048 bytes + vcontext.put("op_endpoint", auth_request.getDestinationUrl(false)); + vcontext.put("openid_parameters", auth_request.getParameterMap()); + template = "openid_form_redirect"; + return false; + } + } catch (OpenIDException e) { + context.put("message", "register_openid_discovery_failed"); + } catch (Exception e) { + context.put("message", "register_openid_discovery_failed"); + } + + return false; + } + + /** + * Proceed with registration using an OpenID identifier + * + * @param context + * @author Markus Lanthaler + */ + protected void confirmOpenIDRegistration(XWikiContext context) + { + XWikiRequest request = context.getRequest(); + VelocityContext vcontext = (VelocityContext) context.get("vcontext"); + + try { + ConsumerManager manager = OpenIDHelper.getConsumerManager(); + + // extract the parameters from the authentication response + // (which comes in as a HTTP request from the OpenID provider) + ParameterList openid_response = new ParameterList(request.getParameterMap()); + + // retrieve the previously stored discovery information + DiscoveryInformation discovered = (DiscoveryInformation) request.getSession().getAttribute("openid-disc"); + + // extract the receiving URL from the HTTP request + StringBuffer receiving_url = request.getRequestURL(); + String query_string = request.getQueryString(); + if (query_string != null && query_string.length() > 0) + receiving_url.append("?").append(request.getQueryString()); + + // verify the response; ConsumerManager needs to be the same + // (static) instance used to place the authentication request + VerificationResult verification = manager.verify(receiving_url.toString(), openid_response, discovered); + + // examine the verification result and extract the verified identifier + Identifier verified = verification.getVerifiedId(); + + if (verified != null) { + vcontext.put("reg", new Integer(2)); + vcontext.put("openid_identifier", verified.getIdentifier()); + + AuthSuccess auth_success = (AuthSuccess) verification.getAuthResponse(); + + String email = null; + String firstname = null; + String lastname = null; + + if (auth_success.hasExtension(AxMessage.OPENID_NS_AX)) { + FetchResponse fetch_resp = (FetchResponse) auth_success.getExtension(AxMessage.OPENID_NS_AX); + email = (String) fetch_resp.getAttributeValues("email").get(0); + firstname = (String) fetch_resp.getAttributeValues("firstname").get(0); + lastname = (String) fetch_resp.getAttributeValues("lastname").get(0); + } + + if (auth_success.getExtension(SRegMessage.OPENID_NS_SREG) instanceof SRegResponse) { + SRegResponse simple_reg_resp = (SRegResponse) auth_success.getExtension(SRegMessage.OPENID_NS_SREG); + + if (email == null) + email = simple_reg_resp.getAttributeValue("email"); + + if (firstname == null && lastname == null) + firstname = simple_reg_resp.getAttributeValue("fullname"); + } + + vcontext.put("email", email); + vcontext.put("first_name", firstname); + vcontext.put("last_name", lastname); + } else { + // Authentication failed, show error message + if (openid_response.getParameter("openid.mode") != null + && openid_response.getParameter("openid.mode").getValue().equals("cancel")) { + context.put("message", "register_openid_discovery_cancelled"); + } else { + if (log.isInfoEnabled() && openid_response.getParameter("error") != null) { + log.info("OpenID login failed (error: " + + openid_response.getParameter("openid.error").getValue() + ")"); + } + context.put("message", "register_openid_discovery_failed"); + } + } + } catch (OpenIDException e) { + context.put("message", "register_openid_discovery_failed"); + } + } + + protected int registerOpenID(XWikiContext context) throws XWikiException + { + XWikiRequest request = context.getRequest(); + String openid_identifier = request.getParameter("openid_identifier"); + String firstname = request.getParameter("register_first_name"); + String lastname = request.getParameter("register_last_name"); + String email = request.getParameter("register_email"); + + return OpenIDHelper.createUser(openid_identifier, firstname, lastname, email, context); + } } Index: core/xwiki-core/src/main/resources/ApplicationResources.properties =================================================================== --- core/xwiki-core/src/main/resources/ApplicationResources.properties (revision 11567) +++ core/xwiki-core/src/main/resources/ApplicationResources.properties (working copy) @@ -110,6 +110,7 @@ attachthisfile=Attach this file username=Username password=Password +openid=OpenID xwikidoc=Documentation documentation=Documentation xwikisyntax=XWiki Syntax @@ -527,12 +528,29 @@ admin.adminappnotinstalled=The administration application is not installed. Since XWiki Enterprise 1.5 the Administration is distributed as an application. You can download it from {0}. #login +login_username_password_desc=with your username and password +login_openid_desc=... or with your OpenID nousername=No user name given +noopenid=Please enter your OpenID identifier to log in nopassword=No password given wronguser=Wrong user name wrongpassword=Wrong password loginfailed=Internal error +openidlogin_cancelled=The login process was cancelled at the OpenID provider site +openid_not_associated=No user was found for this OpenID +#openid form redirect +openid.form_redirect.title=OpenID login in progress +openid.form_redirect.description=You are redirected to your OpenID provider in a few seconds. Please wait... +openid.form_redirect.button=Continue... + +#openid registration form +openid.registration.welcome=If you have an OpenID or i-name you can speed-up your registration by entering it here +openid.registration.openid=OpenID +openid.registration.discovery_submit=Register with OpenID +openid.registration.confirmation=Verify the form below and complete your OpenID registration +openid.registration.confirmation_submit=Create OpenID account + switchto=Switch to sectionEdit=Sectional Editing @@ -619,6 +637,8 @@ registerfailed=Registration has failed registerfailedcode=code registersuccessful=Registration successful +register_openid_discovery_cancelled=The registration with OpenID was cancelled at the OpenID provider site +register_openid_discovery_failed=The registration with OpenID failed due to an internal error leftPanels=Left Panels rightPanels=Right Panels @@ -763,6 +783,7 @@ core.comment.updateClassPropertyName=Updated class property name core.comment.createdUser=Created user core.comment.addedUserToGroup=Added user to group +core.comment.addedOpenIdIdentifier=Added OpenID identifier core.comment.rollback=Rollback to version {0} core.comment.updateContent=Update Content core.comment.uploadAttachmentComment=Upload new attachment {0} Index: core/xwiki-core/src/main/resources/JcrQueries.properties =================================================================== --- core/xwiki-core/src/main/resources/JcrQueries.properties (revision 11567) +++ core/xwiki-core/src/main/resources/JcrQueries.properties (working copy) @@ -3,3 +3,4 @@ getAllDocuments=//element(*, xwiki:document)/@fullName listGroupsForUser=//element(*, xwiki:document)[obj/XWiki/XWikiGroups[@xp:member=:{username} or @xp:member=:{shortname} or @xp:member=:{veryshortname}]/@fullName getAllUsers=//element(*, xwiki:document)[obj/XWiki/XWikiUsers]/@fullName +getUserDocByOpenIdIdentifier=//element(*, xwiki:document)[obj/XWiki/OpenIdIdentifier/@xp:identifier=:{identifier}]/@fullName Index: core/xwiki-core/src/main/resources/queries.hbm.xml =================================================================== --- core/xwiki-core/src/main/resources/queries.hbm.xml (revision 11567) +++ core/xwiki-core/src/main/resources/queries.hbm.xml (working copy) @@ -23,4 +23,7 @@ select doc.fullName from XWikiDocument as doc, BaseObject as obj where obj.name=doc.fullName and obj.className='XWiki.XWikiUsers' + + select doc.fullName from XWikiDocument as doc, BaseObject as obj, StringProperty as prop where doc.fullName=obj.name and obj.className='XWiki.OpenIdIdentifier' and obj.id=prop.id.id and prop.id.name='identifier' and prop.value=:identifier + Index: web/standard/src/main/webapp/templates/login.vm =================================================================== --- web/standard/src/main/webapp/templates/login.vm (revision 11567) +++ web/standard/src/main/webapp/templates/login.vm (working copy) @@ -18,34 +18,71 @@ ### #template("startpage.vm")
-
- -#xwikimessageboxstart($msg.get("login") "") +###xwikimessageboxstart($msg.get("login") "") + +
+

$msg.get("login")

#set($message = $msg.get($xwiki.parseMessage())) #if($message) #error($message) #end - - - - - +
+ + + +
+ + + +

with your username and password

+ + + + + #if(0) - - + + #else - + #end +
+
+ +
+
+ +

... or with your OpenID

+ + + +#if(0) + + +#else + +#end +
+
+
+
-
-#xwikimessageboxend() -
+ +###xwikimessageboxend() + +
## Ensure that the username field of the login form has the focus to make it easy for users to log in quickly