Index: core/xwiki-core/src/main/java/com/xpn/xwiki/plugin/openid/OpenIdHelper.java =================================================================== --- core/xwiki-core/src/main/java/com/xpn/xwiki/plugin/openid/OpenIdHelper.java (revision 0) +++ core/xwiki-core/src/main/java/com/xpn/xwiki/plugin/openid/OpenIdHelper.java (revision 0) @@ -0,0 +1,364 @@ +/* + * 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.plugin.openid; + +import java.net.MalformedURLException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.openid4java.consumer.ConsumerException; +import org.openid4java.consumer.ConsumerManager; +import org.openid4java.server.ServerManager; +import org.xwiki.model.reference.DocumentReference; +import org.xwiki.query.Query; +import org.xwiki.query.QueryException; +import org.xwiki.query.QueryManager; + +import com.ibm.icu.text.MessageFormat; +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.web.XWikiServletURLFactory; + +/** + * OpenID helper class. This singleton class contains various helper methods used for OpenID related tasks. + * + * @since 2.3-milestone-2 + * @version $Id$ + */ +public final class OpenIdHelper +{ + + /** The fullName of the document that contains the class definition of an OpenID identifier. */ + private static final String XWIKI_OPENIDIDENTIFIER_CLASSNAME = "XWiki.OpenIdIdentifier"; + + /** The name of the field representing the user password in the XWiki.XWikiUsers class. */ + private static final String PASSWORD_FIELD_NAME = "password"; + + /** The name of the field representing the identifier in the XWiki.OpenIdIdentifier class. */ + private static final String IDENTIFIER_FIELD_NAME = "identifier"; + + /** String representing a dot (.). */ + private static final String DOT = "."; + + /** The name of the XWiki space. */ + private static final String XWIKI_SPACENAME = "XWiki"; + + /** The name of the XWiki.XWikiUsers document. */ + private static final String XWIKIUSERS_DOCUMENT_NAME = "XWiki.XWikiUsers"; + + /** The log used by this class. */ + private static final Log LOG = LogFactory.getLog(OpenIdHelper.class); + + /** Our instance. */ + private static OpenIdHelper instance; + + /** The Open Id consumer manager this class uses. */ + private ConsumerManager consumerManager; + + /** The Open Id server manager this class uses. */ + private ServerManager serverManager; + + /** + * The constructor instantiates a ConsumerManager and a ServerManager object. + * + * @throws ConsumerException when the consumer manager failed to initialize. + */ + private OpenIdHelper() throws ConsumerException + { + consumerManager = new ConsumerManager(); + serverManager = new ServerManager(); + } + + /** + * Gets the unique ConsumerManager class. + * + * @return the unique ConsumerManager instance + * @throws ConsumerException when the consumer manager failed to initialize. + */ + public static ConsumerManager getConsumerManager() throws ConsumerException + { + if (instance == null) { + instance = new OpenIdHelper(); + } + + return instance.consumerManager; + } + + /** + * Gets the unique ServerManager class. + * + * @param context the context + * @return the unique ServerManager instance + * @throws ConsumerException when the consumer manager failed to initialize. + */ + public static ServerManager getServerManager(XWikiContext context) throws ConsumerException + { + if (instance == null) { + instance = new OpenIdHelper(); + instance.serverManager.setOPEndpointUrl(getOpenIdServerURL(context)); + } + + return instance.serverManager; + } + + /** + * Finds the user belonging to a specific OpenID identifier. + * + * @param openidIdentifier the OpenID identifier to search for + * @param context the 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 openidIdentifier, XWikiContext context) + { + XWiki xwiki = context.getWiki(); + + QueryManager qm = xwiki.getStore().getQueryManager(); + Query searchUser; + try { + searchUser = qm.getNamedQuery("getUserDocByOpenIdIdentifier"); + } catch (QueryException e) { + throw new RuntimeException("Named query 'getUserDocByOpenIdIdentifier' was not found!", e); + } + + searchUser.bindValue(IDENTIFIER_FIELD_NAME, openidIdentifier); + + try { + List foundUsers = searchUser.setLimit(1).execute(); + + if (foundUsers.size() > 0) { + if (LOG.isDebugEnabled()) { + LOG.debug(MessageFormat.format("OpenID {0} already registered.", new String[] {openidIdentifier})); + } + return foundUsers.get(0); + } + + } catch (QueryException e) { + throw new RuntimeException("Failed to execute query getUserDocByOpenIdIdentifier", e); + } + + return null; + } + + /** + * Creates a new OpenID user with a random password. The user doesn't need to know the password - in fact he even + * doesn't need to know of its existence. It's just used to use the {@link PersistentLoginManager} and the + * Authenticator as they are implemented at the moment. + * + * @param openidIdentifier the OpenID identifier + * @param firstname users first name + * @param lastname users last name + * @param email users email address + * @param context the context + * @return a code which describes the success or failure of the method + * @throws XWikiException when the user creation failed + */ + public static int createUser(String openidIdentifier, String firstname, String lastname, String email, + XWikiContext context) throws XWikiException + { + XWiki xwiki = context.getWiki(); + String xwikiname = xwiki.clearName(MessageFormat.format("{0}{1}", new String[] {firstname, lastname}), context); + + DocumentReference userdocRef = new DocumentReference(context.getDatabase(), XWIKI_SPACENAME, xwikiname); + // Generate a unique document name for the new user + XWikiDocument userdoc = xwiki.getDocument(userdocRef, context); + while (!userdoc.isNew()) { + userdocRef = + new DocumentReference(context.getDatabase(), XWIKI_SPACENAME, xwikiname + + xwiki.generateRandomString(5)); + userdoc = xwiki.getDocument(userdocRef, context); + } + + if (LOG.isDebugEnabled()) { + LOG.debug("Creating user for OpenID " + openidIdentifier); + } + + String content = "{{include document=\"XWiki.XWikiUserSheet\" /}}"; + String parent = XWIKIUSERS_DOCUMENT_NAME; + Map map = new HashMap(); + map.put("active", "1"); + map.put("first_name", StringUtils.isEmpty(firstname) ? openidIdentifier : firstname); + + if (lastname != null) { + map.put("last_name", lastname); + } + if (email != null) { + map.put("email", email); + } + + String password = xwiki.generateRandomString(255); + map.put(PASSWORD_FIELD_NAME, password); + + int result = xwiki.createUser(xwikiname, map, parent, content, XWikiDocument.XWIKI20_SYNTAXID, "edit", context); + if (result == 1) { + // change the return value to output a different message for OpenID users + result = 3; + + userdoc = xwiki.getDocument(XWIKI_SPACENAME + DOT + xwikiname, context); + + if (!attachOpenIdToUser(userdoc, openidIdentifier, context)) { + if (LOG.isDebugEnabled()) { + LOG.debug("Deleting previously created document for OpenID user " + openidIdentifier + + ". OpenID identifier is already in use."); + } + xwiki.deleteDocument(userdoc, false, context); + result = -13; + } + + } + return result; + } + + /** + * Attaches an OpenID identifier to a document. The method assures that the identifier is unique. If the OpenID + * identifier is already used, the method fails and returns false. If the the document has already + * attached an OpenID it will be replaced by this method. The document is automatically saved by this + * method. + * + * @param doc document to which the OpenID identifier should be attached + * @param openidIdentifier the OpenID identifier to attach + * @param context the context + * @return true if attaching the OpenID identifier was successful, otherwise false. + * @throws XWikiException when saving the user document fails + */ + public static synchronized boolean attachOpenIdToUser(XWikiDocument doc, String openidIdentifier, + XWikiContext context) throws XWikiException + { + XWiki xwiki = context.getWiki(); + + if (findUser(openidIdentifier, context) != null) { + if (LOG.isDebugEnabled()) { + LOG.debug("OpenID " + openidIdentifier + " is already registered"); + } + return false; + } + + BaseClass baseclass = getOpenIdIdentifierClass(context); + + BaseObject object = doc.getObject(XWIKI_OPENIDIDENTIFIER_CLASSNAME); + if (object == null) { + object = (BaseObject) baseclass.newObject(context); + object.setName(doc.getFullName()); + doc.addObject(baseclass.getName(), object); + } + object.setStringValue(IDENTIFIER_FIELD_NAME, openidIdentifier); + + xwiki.saveDocument(doc, context.getMessageTool().get("core.comment.addedOpenIdIdentifier"), context); + + return true; + } + + /** + * Retrieves the password for an OpenID identifier. Since for all OpenID users a random password which the user + * doesn't know is created automatically, we need some way to retrieve it. This method does exactly that. + * + * @param openidIdentifier the OpenID identifier + * @param context the context + * @return the internal used password for the OpenID user or null if the user was not found + * @throws XWikiException when the user document could not be retrieve against the wiki. + */ + public static String getOpenIdUserPassword(String openidIdentifier, XWikiContext context) throws XWikiException + { + String xwikiname = findUser(openidIdentifier, context); + + XWikiDocument doc = context.getWiki().getDocument(xwikiname, context); + // We only allow empty password from users having a XWikiUsers object. + if (doc.getObject(XWIKI_OPENIDIDENTIFIER_CLASSNAME) != null + && doc.getObject(XWIKIUSERS_DOCUMENT_NAME) != null) { + return doc.getStringValue(XWIKIUSERS_DOCUMENT_NAME, PASSWORD_FIELD_NAME); + } + + return null; + } + + /** + * Retrieves the OpenID identifier for an user. + * + * @param username the user + * @param context the context + * @return the OpenID identifier for the passed user name or null if the user was not found + * @throws XWikiException when the user document could not be retrieve against the wiki. + */ + public static String getUserOpenId(String username, XWikiContext context) throws XWikiException + { + XWikiDocument doc = context.getWiki().getDocument(username, context); + if (doc != null && doc.getObject(XWIKIUSERS_DOCUMENT_NAME) != null) { + return getOpenIdServerURL(context) + doc.getName(); + } + + return null; + } + + /** + * Returns the URL of the OpenID server. + * + * @param context the context + * @return the URL of the OpenID server endpoint. + */ + public static String getOpenIdServerURL(XWikiContext context) + { + try { + return ((XWikiServletURLFactory) context.getURLFactory()).getServerURL(context).toString() + + ((XWikiServletURLFactory) context.getURLFactory()).getContextPath() + "openid/"; + } catch (MalformedURLException e) { + // should not happen :-) + return null; + } + } + + /** + * Gets the OpenIDIdentifer class. Verifies 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 needsUpdate = false; + + doc = xwiki.getDocument(XWIKI_OPENIDIDENTIFIER_CLASSNAME, context); + + BaseClass bclass = doc.getxWikiClass(); + bclass.setName(XWIKI_OPENIDIDENTIFIER_CLASSNAME); + + needsUpdate |= bclass.addTextField(IDENTIFIER_FIELD_NAME, "Identifier", 2048); + needsUpdate |= bclass.addTextField(PASSWORD_FIELD_NAME, "Password", 255); + + if (needsUpdate) { + xwiki.saveDocument(doc, context); + } + + return bclass; + } +} Index: core/xwiki-core/src/main/java/com/xpn/xwiki/web/Utils.java =================================================================== --- core/xwiki-core/src/main/java/com/xpn/xwiki/web/Utils.java (revision 27619) +++ core/xwiki-core/src/main/java/com/xpn/xwiki/web/Utils.java (working copy) @@ -110,6 +110,7 @@ } else { response.setContentType("text/html; charset=" + context.getWiki().getEncoding()); } + String action = context.getAction(); if ((!"download".equals(action)) && (!"skin".equals(action))) { @@ -134,6 +135,10 @@ } } } + + if("openid_xrds".equals(action)) { + response.setContentType("application/xrds+xml"); + } if (("download".equals(action)) || ("skin".equals(action))) { // Set a nocache to make sure these files are not cached by proxies 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 27619) +++ core/xwiki-core/src/main/java/com/xpn/xwiki/web/RegisterAction.java (working copy) @@ -20,41 +20,283 @@ */ 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.openid.OpenIdHelper; + +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; + +/** + * Action used to register new users. This action implements both, registration for classical user accounts with user + * name and password and users using OpenID. + */ +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; + } + + /** + * Starts registration of an OpenID user. The OpenID provider belonging to the entered OpenID identifier is searched + * and the user is redirected to it to authenticate there. This processed is used to assure that the entered OpenID + * is valid and in possession of that user. If discovery fails, an error message is shown. + * + * @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 == null || openid_identifier.equals("")) { + vcontext.put("reg", new Integer(-14)); + return false; + } + + try { + ConsumerManager manager = OpenIdHelper.getConsumerManager(); + + 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); + request.getSession().setAttribute("openid-discovery", discovered); + + // obtain a AuthRequest message to be sent to the OpenID provider + AuthRequest auth_request = manager.authenticate(discovered, return_to_url); + + // set the realm + auth_request.setRealm(((XWikiServletURLFactory) context.getURLFactory()).getServerURL(context).toString() + + ((XWikiServletURLFactory) context.getURLFactory()).getContextPath()); + + // attribute exchange (request user data from the OP to speed-up the registration process) + 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); + + auth_request.addExtension(att_exchange); + auth_request.addExtension(simple_reg_req); + + if (discovered.isVersion2()) { + // 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; + } 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) + sendRedirect(response, auth_request.getDestinationUrl(true)); + return true; + } + } catch (OpenIDException e) { + context.put("message", "register_openid_discovery_failed"); + } catch (Exception e) { + context.put("message", "register_openid_discovery_failed"); + } + + return false; + } + + /** + * Checks the response of the OpenID provider (OP) and outputs a confirmation form. If the OP returns an error or + * the user cancelled at its site, an error message is shown to the user. + * + * @param context + * @throws XWikiException + * @author Markus Lanthaler + */ + protected void confirmOpenIdRegistration(XWikiContext context) throws XWikiException + { + 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-discovery"); + + // 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) { + // check if this OpenID is already registered + if (OpenIdHelper.findUser(verified.getIdentifier(), context) != null) { + vcontext.put("reg", new Integer(-13)); + vcontext.put("openid_identifier", verified.getIdentifier()); + return; + } + + // OpenID not used yet, continue with registration (ask the user for confirmation) + 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 and log 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"); + } + } + + /** + * Completes the registration of an OpenID user. The user name is created automatically based on the OpenID + * identifier by {@link OpenIdHelper#openIdIdentifierToUsername}. + * + * @param context + * @return an status code describing the success or failure of the registration + * @throws XWikiException + * @see OpenIdHelper#openIdIdentifierToUsername + */ + 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"); + + int result = OpenIdHelper.createUser(openid_identifier, firstname, lastname, email, context); + if (result == 3) { + // user registration successful + VelocityContext vcontext = (VelocityContext) context.get("vcontext"); + vcontext.put("username", context.getWiki().getUserName(OpenIdHelper.findUser(openid_identifier, context), + context)); + vcontext.put("openid_identifier", openid_identifier); + } + + return result; + } } Index: core/xwiki-core/src/main/java/com/xpn/xwiki/web/AttachOpenIdAction.java =================================================================== --- core/xwiki-core/src/main/java/com/xpn/xwiki/web/AttachOpenIdAction.java (revision 0) +++ core/xwiki-core/src/main/java/com/xpn/xwiki/web/AttachOpenIdAction.java (revision 0) @@ -0,0 +1,305 @@ +/* + * 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.web; + +import java.util.List; + +import com.xpn.xwiki.XWikiContext; +import com.xpn.xwiki.XWikiException; +import com.xpn.xwiki.plugin.openid.OpenIdHelper; + +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.velocity.VelocityContext; + +import org.openid4java.consumer.ConsumerManager; +import org.openid4java.consumer.VerificationResult; +import org.openid4java.discovery.Identifier; +import org.openid4java.discovery.DiscoveryInformation; +import org.openid4java.message.AuthRequest; +import org.openid4java.message.ParameterList; +import org.openid4java.OpenIDException; + +/** + * Action used to attach an OpenID to already existing users. + * + * @since 2.3-milestone-2 + * @version $Id$ + */ +public class AttachOpenIdAction extends XWikiAction +{ + /** The name of the OpenId parameter representing the Open Id mode. */ + private static final String OPENID_MODE_PARAMETER = "openid.mode"; + + /** The fullName of the document holding the class definition of an Open ID identifier. */ + private static final String XWIKI_OPENID_IDENTIFIER_CLASSNAME = "XWiki.OpenIdIdentifier"; + + /** The name of the velocity context key for the username variable. */ + private static final String USERNAME_VCONTEXT_KEY = "username"; + + /** The name of the velocity context key for the Open Id identifier variable. */ + private static final String OPENID_IDENTIFIER_VCONTEXT_KEY = "openid_identifier"; + + /** The context key for an error or information message. */ + private static final String MESSAGE_CONTEXT_KEY = "message"; + + /** The message when Open ID discovery failed. */ + private static final String REGISTER_OPENID_DISCOVERY_FAILED_MESSAGE_VALUE = "register_openid_discovery_failed"; + + /** The name of the session attribute representing OpenId discovery information. */ + private static final String OPENID_DISCOVERY_SESSION_KEY = "openid-discovery"; + + /** The key associated with the velocity context in the xwiki context map. */ + private static final String VCONTEXT_CONTEXT_KEY = "vcontext"; + + /** The name of the 'status' variable pushed in the velocity context. */ + private static final String STATUS_VCONTEXT_KEY = "status"; + + /** The name of the velocity template for attaching OpenId. */ + private static final String ATTACH_OPENID_TEMPLATE_NAME = "attach_openid"; + + /** The log used by this class. */ + private static final Log LOG = LogFactory.getLog(AttachOpenIdAction.class); + + /** The name of the velocity template rendered by this servlet. */ + private String template = ATTACH_OPENID_TEMPLATE_NAME; + + /** + * {@inheritDoc} + */ + @Override + public boolean action(XWikiContext context) throws XWikiException + { + // Only the user itself or an administrator is allowed to attach an OpenID + if (!(context.getDoc().getFullName().equals(context.getXWikiUser().getUser())) + && !context.getWiki().getRightService().hasAdminRights(context)) { + template = "accessdenied"; + return true; + } + + XWikiRequest request = context.getRequest(); + XWikiResponse response = context.getResponse(); + VelocityContext vcontext = (VelocityContext) context.get(VCONTEXT_CONTEXT_KEY); + + template = ATTACH_OPENID_TEMPLATE_NAME; + + String attachOpenid = request.getParameter(ATTACH_OPENID_TEMPLATE_NAME); + if (StringUtils.equals("discover-openid", attachOpenid)) { + if (discoverOpenID(context)) { + return false; + } + } else if (StringUtils.equals("confirm", attachOpenid)) { + vcontext.put(STATUS_VCONTEXT_KEY, new Integer(confirmAttachingOpenId(context))); + } else if (StringUtils.equals("attach-openid", attachOpenid)) { + vcontext.put(STATUS_VCONTEXT_KEY, new Integer(attachOpenID(context))); + } + + String redirect = Utils.getRedirect(request, null); + if (redirect == null) { + return true; + } else { + sendRedirect(response, redirect); + return false; + } + } + + /** + * {@inheritDoc} + */ + @Override + public String render(XWikiContext context) throws XWikiException + { + return template; + } + + /** + * Starts the process of attaching an OpenID to an existing user. The OpenID provider belonging to the entered + * OpenID identifier is searched and the user is redirected to it to authenticate there. This processed is used to + * assure that the entered OpenID is valid and in possession of that user. If discovery fails, an error message is + * shown. + * + * @param context the XWiki context when discovering OpenId. + * @return returns true if a redirect was sent, otherwise false. + */ + protected boolean discoverOpenID(XWikiContext context) + { + XWikiRequest request = context.getRequest(); + XWikiResponse response = context.getResponse(); + VelocityContext vcontext = (VelocityContext) context.get(VCONTEXT_CONTEXT_KEY); + + // Check for empty OpenID identifier + String openidIdentifier = request.getParameter(OPENID_IDENTIFIER_VCONTEXT_KEY); + if (openidIdentifier == null || openidIdentifier.equals("")) { + vcontext.put(STATUS_VCONTEXT_KEY, new Integer(-14)); + return false; + } + + try { + ConsumerManager manager = OpenIdHelper.getConsumerManager(); + + String returnToUrl = context.getDoc().getExternalURL("attachopenid", "attach_openid=confirm", context); + + // perform discovery on the user-supplied identifier + List discoveries = manager.discover(openidIdentifier); + + // attempt to associate with the OpenID provider and retrieve one service endpoint for authentication + DiscoveryInformation discovered = manager.associate(discoveries); + request.getSession().setAttribute(OPENID_DISCOVERY_SESSION_KEY, discovered); + + // obtain a AuthRequest message to be sent to the OpenID provider + AuthRequest authRequest = manager.authenticate(discovered, returnToUrl); + + // set the realm + authRequest.setRealm(((XWikiServletURLFactory) context.getURLFactory()).getServerURL(context).toString() + + ((XWikiServletURLFactory) context.getURLFactory()).getContextPath()); + + if (discovered.isVersion2()) { + // OpenID 2.0 supports HTML form redirection which allows payloads >2048 bytes + vcontext.put("op_endpoint", authRequest.getDestinationUrl(false)); + vcontext.put("openid_parameters", authRequest.getParameterMap()); + template = "openid_form_redirect"; + return false; + } 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) + sendRedirect(response, authRequest.getDestinationUrl(true)); + return true; + } + } catch (OpenIDException e) { + context.put(MESSAGE_CONTEXT_KEY, REGISTER_OPENID_DISCOVERY_FAILED_MESSAGE_VALUE); + } catch (Exception e) { + context.put(MESSAGE_CONTEXT_KEY, REGISTER_OPENID_DISCOVERY_FAILED_MESSAGE_VALUE); + } + + return false; + } + + /** + * Checks the response of the OpenID provider (OP) and outputs a confirmation form. If the OP returns an error or + * the user cancelled at its site, an error message is shown to the user. + * + * @param context the XWiki context when confirming attaching open ID. + * + * @return a code associated with the result of the operation. + */ + protected int confirmAttachingOpenId(XWikiContext context) + { + XWikiRequest request = context.getRequest(); + VelocityContext vcontext = (VelocityContext) context.get(VCONTEXT_CONTEXT_KEY); + + try { + ConsumerManager manager = OpenIdHelper.getConsumerManager(); + + // extract the parameters from the authentication response + // (which comes in as a HTTP request from the OpenID provider) + ParameterList openidResponse = new ParameterList(request.getParameterMap()); + + // retrieve the previously stored discovery information + DiscoveryInformation discovered = + (DiscoveryInformation) request.getSession().getAttribute(OPENID_DISCOVERY_SESSION_KEY); + + // extract the receiving URL from the HTTP request + StringBuffer receivingUrl = request.getRequestURL(); + String queryString = request.getQueryString(); + if (!StringUtils.isBlank(queryString)) { + receivingUrl.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(receivingUrl.toString(), openidResponse, discovered); + + // examine the verification result and extract the verified identifier + Identifier verified = verification.getVerifiedId(); + + if (verified != null) { + vcontext.put(USERNAME_VCONTEXT_KEY, + context.getWiki().getUserName(context.getDoc().getFullName(), context)); + + // check if this OpenID is already registered + if (OpenIdHelper.findUser(verified.getIdentifier(), context) != null) { + vcontext.put(OPENID_IDENTIFIER_VCONTEXT_KEY, verified.getIdentifier()); + return -3; + } + vcontext.put(OPENID_IDENTIFIER_VCONTEXT_KEY, verified.getIdentifier()); + + // Check if the user has already attached an OpenID + if (context.getDoc().getObject(XWIKI_OPENID_IDENTIFIER_CLASSNAME) != null) { + vcontext.put("current_openid_identifier", + context.getDoc().getStringValue(XWIKI_OPENID_IDENTIFIER_CLASSNAME, "identifier")); + return -1; + } + + // Ask the user for confirmation + return 1; + } else { + // authentication failed, show and log error message + if (openidResponse.getParameter(OPENID_MODE_PARAMETER) != null + && StringUtils.equals("cancel", openidResponse.getParameter(OPENID_MODE_PARAMETER).getValue())) { + // OpenID discovery cancelled + return -4; + } else { + if (LOG.isInfoEnabled() && openidResponse.getParameter("error") != null) { + LOG.info("OpenID login failed (error: " + + openidResponse.getParameter("openid.error").getValue() + ")"); + } + // OpenID discovery failed + return -5; + } + } + } catch (OpenIDException e) { + // Internal error + return -6; + } + } + + /** + * Attaches an OpenID to an existing user. The method checks if the password is correct and if so the OpenID is + * attached to the user. + * + * @param context the XWiki context when attaching open Id. + * @return an status code describing the success or failure of the process + * @throws XWikiException when attaching Open Id to user failed. + */ + protected int attachOpenID(XWikiContext context) throws XWikiException + { + XWikiRequest request = context.getRequest(); + VelocityContext vcontext = (VelocityContext) context.get(VCONTEXT_CONTEXT_KEY); + String openidIdentifier = request.getParameter(OPENID_IDENTIFIER_VCONTEXT_KEY); + + if (openidIdentifier == null || openidIdentifier.length() == 0) { + // No OpenID passed + return -2; + } + + // Attach the OpenID + if (!OpenIdHelper.attachOpenIdToUser(context.getDoc(), openidIdentifier, context)) { + // Internal error + return -6; + } + + // Successfully attached the OpenID to the user account + vcontext.put(USERNAME_VCONTEXT_KEY, context.getWiki().getUserName(context.getXWikiUser().getUser(), context)); + vcontext.put(OPENID_IDENTIFIER_VCONTEXT_KEY, openidIdentifier); + return 2; + } +} Index: core/xwiki-core/src/main/java/com/xpn/xwiki/web/OpenIdServerServlet.java =================================================================== --- core/xwiki-core/src/main/java/com/xpn/xwiki/web/OpenIdServerServlet.java (revision 0) +++ core/xwiki-core/src/main/java/com/xpn/xwiki/web/OpenIdServerServlet.java (revision 0) @@ -0,0 +1,456 @@ +/* + * 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.web; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +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.openid.OpenIdHelper; +import com.xpn.xwiki.user.api.XWikiUser; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.velocity.VelocityContext; + +import org.openid4java.consumer.ConsumerException; +import org.openid4java.message.DirectError; +import org.openid4java.message.Message; +import org.openid4java.message.ParameterList; +import org.openid4java.server.ServerManager; +import org.xwiki.container.Container; +import org.xwiki.container.servlet.ServletContainerInitializer; +import org.xwiki.context.Execution; +import org.xwiki.velocity.VelocityManager; + +/** + * Action that implements the OpenID server support. + * + * @since 2.3-milestone-2 + * @version $Id$ + */ +public final class OpenIdServerServlet extends HttpServlet +{ + /** The log used by this class. */ + private static final Log log = LogFactory.getLog(OpenIdServerServlet.class); + + /** The name of the velocity template rendererd by this servlet. */ + private String template = "attach_openid"; + + /** The address to use as a home page where the users are redirected. */ + private String home = "bin/view/Main/"; + + /** + * {@inheritDoc} + * + * @see javax.servlet.GenericServlet#init() + */ + @Override + public void init() throws ServletException + { + super.init(); + // TODO: we cannot use the XWiki API to determine the right URL, because this is a servlet and the core + // is reachable mainly from Struts. Getting access to the core requires too much duplication, so for the + // moment we're going the easy way: hardcoded values. + String homeParameter = getInitParameter("homePage"); + if (homeParameter != null) { + this.home = homeParameter; + } + } + + /** + * {@inheritDoc} + */ + @Override + public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + { + handleRequest(request, response); + } + + /** + * {@inheritDoc} + */ + @Override + public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + { + handleRequest(request, response); + } + + /** + * {@inheritDoc} + */ + @Override + public void doHead(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + { + handleRequest(request, response); + } + + /** + * Sets up a context for the request. It's more or less the same as + * {@link com.xpn.xwiki.web.XWikiAction#action(XWikiContext)}. + * + * @param request + * @param response + * @throws ServletException + * @throws IOException + * @see + * @link com.xpn.xwiki.web.XWikiAction#action(XWikiContext) + */ + protected void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, + IOException + { + XWikiRequest xwiki_request = new XWikiServletRequest(request); + XWikiResponse xwiki_response = new XWikiServletResponse(response); + XWikiContext context = null; + + try { + context = + Utils.prepareContext("openid", xwiki_request, xwiki_response, new XWikiServletContext(this + .getServletContext())); + + // Initialize the Container component which is the new of transporting the Context in the new + // component architecture. + ServletContainerInitializer containerInitializer = + (ServletContainerInitializer) Utils.getComponent(ServletContainerInitializer.class); + + containerInitializer.initializeRequest(context.getRequest().getHttpServletRequest(), context); + containerInitializer.initializeResponse(context.getResponse().getHttpServletResponse()); + containerInitializer.initializeSession(context.getRequest().getHttpServletRequest()); + + // Verify that the requested wiki exists + XWiki xwiki; + try { + xwiki = XWiki.getXWiki(context); + } catch (XWikiException e) { + // Should we output an error message here? + return; + } + + XWikiURLFactory urlf = xwiki.getURLFactoryService().createURLFactory(context.getMode(), context); + context.setURLFactory(urlf); + + context.put("ajax", false); + + VelocityManager velocityManager = (VelocityManager) Utils.getComponent(VelocityManager.class); + VelocityContext vcontext = velocityManager.getVelocityContext(); + + XWikiDocument doc; + context.getWiki().prepareResources(context); + context.getWiki().setPhonyDocument("openid", context, vcontext); + doc = context.getDoc(); + + context.put("doc", doc); + vcontext.put("doc", doc.newDocument(context)); + vcontext.put("cdoc", vcontext.get("doc")); + XWikiDocument tdoc = doc.getTranslatedDocument(context); + context.put("tdoc", tdoc); + vcontext.put("tdoc", tdoc.newDocument(context)); + + // Execute business logic + if (action(context) == true) + render(context); + + // Let's make sure we have flushed content and closed + try { + context.getResponse().getWriter().flush(); + } catch (Throwable e) { + // This might happen if the connection was closed, for example. + // If we can't flush, then there's nothing more we can send to the client. + } + + // Make sure we cleanup database connections + // There could be cases where we have some + if ((context != null) && (xwiki != null)) { + xwiki.getStore().cleanUp(context); + } + + } catch (Exception e) { + // do nothing... + } finally { + + if (context != null) { + // Cleanup components + Container container = (Container) Utils.getComponent(Container.class); + Execution execution = (Execution) Utils.getComponent(Execution.class); + + // We must ensure we clean the ThreadLocal variables located in the Container and Execution + // components as otherwise we will have a potential memory leak. + container.removeRequest(); + container.removeResponse(); + container.removeSession(); + execution.removeContext(); + } + } + } + + /** + * Executes all actions. + * + * @param context the used context + * @return TRUE if a template should be rendered, FALSE if no template should be + * rendered or a redirect was sent. + * @throws XWikiException + */ + private boolean action(XWikiContext context) throws XWikiException + { + XWikiRequest request = context.getRequest(); + XWikiResponse response = context.getResponse(); + boolean enabled = "1".equals(context.getWiki().Param("xwiki.authentication.openid.server.enabled")); + + // Check if a XRDS document was requested + String req_user = request.getRequestURI().substring(request.getContextPath().length() + 8); + if ("xrds.xml".equals(req_user)) { + outputXRDS(null, context); // global XRDS document + return true; + } else if ("xrds.xml".equals(req_user.substring(req_user.indexOf("/") + 1))) { + outputXRDS(req_user.substring(0, req_user.indexOf("/")), context); // user XRDS document + return true; + } else if (req_user.length() > 0 && req_user.contains("/") == false) { + showUserPage(req_user, context); + return true; + } + + ServerManager manager = null; + try { + manager = OpenIdHelper.getServerManager(context); + } catch (ConsumerException e) { + if (log.isInfoEnabled()) { + log.error("Couldn't get a ServerManager instance."); + } + + template = "exception"; + return true; + } + + ParameterList request_parameters = new ParameterList(request.getParameterMap()); + Message response_message; + String response_text = null; + + String mode = request.getParameter("openid.mode"); + if (enabled == false || request_parameters.getParameters().size() == 0) { + // No OpenID action, show homepage and publish XRDS location via HTTP header + VelocityContext vcontext = (VelocityContext) context.get("vcontext"); + vcontext.put("enabled", enabled); + vcontext.put("openid_server_url", OpenIdHelper.getOpenIdServerURL(context)); + + response.setHeader("X-XRDS-Location", OpenIdHelper.getOpenIdServerURL(context) + "xrds.xml"); + template = "openid_server_homepage"; + return true; + + } else if ("associate".equals(mode)) { + // Process an association request + response_message = manager.associationResponse(request_parameters); + response_text = response_message.keyValueFormEncoding(); + + } else if ("checkid_setup".equals(mode)) { + // Process an authentication request setup request + return processCheckIdSetup(manager, context); + + } else if ("checkid_immediate".equals(mode)) { + // Immediate authentication fails always since we don't support it at the moment + response_message = + manager.authResponse(request_parameters, request.getParameter("openid.claimed_id"), request + .getParameter("openid.identity"), false); + + sendRedirect(response, response_message.getDestinationUrl(true)); + return false; + + } else if ("check_authentication".equals(mode)) { + // Processing a verification request + response_message = manager.verify(request_parameters); + response_text = response_message.keyValueFormEncoding(); + + } else { + // Error response + response_message = DirectError.createDirectError("Unknown request"); + response_text = response_message.keyValueFormEncoding(); + } + + // Try to directly output the response + try { + context.getResponse().getWriter().write(response_text); + return false; + } catch (IOException e) { + if (log.isInfoEnabled()) { + log.error("Couldn't output the response."); + } + + template = "exception"; + return true; + } + } + + /** + * Renders the template and outputs it. + * + * @param context the context + * @throws XWikiException + */ + protected void render(XWikiContext context) throws XWikiException + { + String page = Utils.getPage(context.getRequest(), template); + Utils.parseTemplate(page, true, context); + } + + /** + * Helper method to process checkid_setup requests. + * + * @param manager the OpenID server manager instance + * @param context the context + * @return TRUE if a template should be rendered, FALSE if no template should be + * rendered or a redirect was sent. + * @throws XWikiException + */ + private boolean processCheckIdSetup(ServerManager manager, XWikiContext context) throws XWikiException + { + XWikiRequest request = context.getRequest(); + XWikiResponse response = context.getResponse(); + ParameterList request_parameters = new ParameterList(request.getParameterMap()); + Message response_message; + + // First check if the user is logged in. If not show the login form + XWikiUser user = context.getWiki().checkAuth(context); + if ((user == null) && ((user = context.getXWikiUser()) == null)) { + context.getWiki().getAuthService().showLogin(context); + return false; + } + + if (request.getParameter("confirmation") == null) { + // ask the user to confirm the authentication request + VelocityContext vcontext = (VelocityContext) context.get("vcontext"); + vcontext.put("openid_server_url", OpenIdHelper.getOpenIdServerURL(context)); + vcontext.put("openid_identifier", OpenIdHelper.getUserOpenId(user.getUser(), context)); + if (request_parameters.hasParameter("openid.realm")) { + vcontext.put("site", request_parameters.getParameterValue("openid.realm")); + } else { + vcontext.put("site", request_parameters.getParameterValue("openid.return_to")); + } + template = "confirm_openid_authentication_request"; + + // store the request parameters because they are needed to create the response afterwards + request.getSession().setAttribute("openid-authentication-parameterlist", request_parameters); + return true; + + } else if (request.getParameter("login") != null) { + // the user confirmed the authentication request + String identity = OpenIdHelper.getUserOpenId(user.getUser(), context); + String claimed_id = OpenIdHelper.getUserOpenId(user.getUser(), context); + + request_parameters = + (ParameterList) request.getSession().getAttribute("openid-authentication-parameterlist"); + request.getSession().removeAttribute("openid-authentication-parameterlist"); + + response_message = manager.authResponse(request_parameters, identity, claimed_id, true); + sendRedirect(response, response_message.getDestinationUrl(true)); + return false; + + + } else { + // the user cancelled the authentication request + String identity = OpenIdHelper.getUserOpenId(user.getUser(), context); + String claimed_id = OpenIdHelper.getUserOpenId(user.getUser(), context); + + request_parameters = + (ParameterList) request.getSession().getAttribute("openid-authentication-parameterlist"); + request.getSession().removeAttribute("openid-authentication-parameterlist"); + + response_message = manager.authResponse(request_parameters, identity, claimed_id, false); + sendRedirect(response, response_message.getDestinationUrl(true)); + return false; + } + } + + /** + * Helper method to output the gloabal or a user specific XRDS document. + * + * @param req_user username if the XRDS of a specific user is requested, otherwise null. + * @param context + * @return + */ + private void outputXRDS(String username, XWikiContext context) throws XWikiException + { + VelocityContext vcontext = (VelocityContext) context.get("vcontext"); + vcontext.put("openid_server_url", OpenIdHelper.getOpenIdServerURL(context)); + + // Set the action to openid_xrds so that com.xpn.xwiki.web.Utils#parseTemplate uses the right content type + context.setAction("openid_xrds"); + + if (username == null) { + // Output XRDS file + template = "openid_xrds"; + } else { + // Output user XRDS file + template = "openid_user_xrds"; + vcontext.put("openid_identifier", OpenIdHelper.getUserOpenId("XWiki." + username, context)); + } + } + + /** + * Helper method to display an user profile page. + * + * @param username the username + * @param context the context + * @throws XWikiException + */ + private void showUserPage(String username, XWikiContext context) throws XWikiException + { + // Publish the XRDS location via HTTP header + context.getResponse().setHeader("X-XRDS-Location", + OpenIdHelper.getOpenIdServerURL(context) + username + "/xrds.xml"); + + + VelocityContext vcontext = (VelocityContext) context.get("vcontext"); + username = "XWiki." + username; + vcontext.put("username", context.getWiki().getUserName(username, context)); + + template = "openid_user_profile"; + } + + /** + * Redirects to another page. + * + * @param response XWikiResponse object + * @param page redirection target + * @throws XWikiException + */ + private void sendRedirect(XWikiResponse response, String page) throws XWikiException + { + try { + if (page != null) { + response.sendRedirect(page); + } + } catch (IOException e) { + Object[] args = {page}; + throw new XWikiException(XWikiException.MODULE_XWIKI_APP, + XWikiException.ERROR_XWIKI_APP_REDIRECT_EXCEPTION, + "Exception while sending redirect to page {0}", + e, + args); + } + } +} Index: core/xwiki-core/src/main/java/com/xpn/xwiki/XWiki.java =================================================================== --- core/xwiki-core/src/main/java/com/xpn/xwiki/XWiki.java (revision 27619) +++ core/xwiki-core/src/main/java/com/xpn/xwiki/XWiki.java (working copy) @@ -3730,7 +3730,7 @@ { return generateRandomString(size); } - + public int createUser(String xwikiname, Map map, String parent, String content, String syntaxId, String userRights, XWikiContext context) throws XWikiException { 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 27619) +++ core/xwiki-core/src/main/java/com/xpn/xwiki/user/impl/xwiki/XWikiAuthServiceImpl.java (working copy) @@ -550,12 +550,18 @@ 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/user/impl/xwiki/MyFormAuthenticator.java =================================================================== --- core/xwiki-core/src/main/java/com/xpn/xwiki/user/impl/xwiki/MyFormAuthenticator.java (revision 27619) +++ 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.apache.velocity.VelocityContext; +import org.openid4java.OpenIDException; +import org.openid4java.consumer.ConsumerManager; +import org.openid4java.consumer.VerificationResult; +import org.openid4java.discovery.DiscoveryInformation; +import org.openid4java.discovery.Identifier; +import org.openid4java.message.AuthRequest; +import org.openid4java.message.ParameterList; import org.securityfilter.authenticator.Authenticator; import org.securityfilter.authenticator.FormAuthenticator; import org.securityfilter.filter.SecurityRequestWrapper; @@ -38,7 +47,9 @@ import com.xpn.xwiki.XWikiContext; import com.xpn.xwiki.XWikiException; +import com.xpn.xwiki.plugin.openid.OpenIdHelper; import com.xpn.xwiki.web.SavedRequestRestorerFilter; +import com.xpn.xwiki.web.XWikiServletURLFactory; public class MyFormAuthenticator extends FormAuthenticator implements XWikiAuthenticator { @@ -124,7 +135,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, @@ -133,7 +144,6 @@ String username = convertUsername(this.persistentLoginManager.getRememberedUsername(request, response), context); String password = this.persistentLoginManager.getRememberedPassword(request, response); - Principal principal = request.getUserPrincipal(); // 1) if user is not already authenticated, authenticate @@ -165,13 +175,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 (StringUtils.equalsIgnoreCase(request.getParameter("authentication_method") ,"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 (!StringUtils.isBlank(request.getParameter("openid.mode"))) { + // The open ID provider gave a response. Let's process it. + return processOpenIdLoginResponse(request, response, context); + } + else { + // 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); + } } return false; } @@ -181,6 +205,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 @@ -195,7 +222,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(); } @@ -221,7 +249,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 { @@ -247,11 +276,179 @@ } /** + * Processes an OpenID login. It redirects the user as part of a normal OpenID login process to the OpenID provider. + * The response is handled by {@link MyFormAuthenticator#processOpenIdLoginResponse processOpenIdLoginResponse}. + * 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 the OpenID identifier + * @param rememberme "true" if the login should be persistent, null or + * "false" otherwise. + * @param request the request object + * @param response the response object + * @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 (StringUtils.isBlank("openid_identifier")) { + context.put("message", "noopenid"); + return false; + } + + try { + ConsumerManager manager = OpenIdHelper.getConsumerManager(); + + String return_to_url = + context.getWiki().getExternalURL("XWiki.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); + + // set the realm + auth_request.setRealm(((XWikiServletURLFactory) context.getURLFactory()).getServerURL(context).toString() + + ((XWikiServletURLFactory) context.getURLFactory()).getContextPath()); + + 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 + VelocityContext vcontext = (VelocityContext) context.get("vcontext"); + vcontext.put("op_endpoint", auth_request.getDestinationUrl(false)); + vcontext.put("openid_parameters", auth_request.getParameterMap()); + + String redirect_form = context.getWiki().parseTemplate("openid_form_redirect.vm", context); + + response.getOutputStream().print(redirect_form); + + // Close the output stream - otherwise the LOGin form documented is also written to it + response.getOutputStream().close(); + } 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; + } + + /** + * Processes the response of an OpenID provider to complete the LOGin process. Checks the response of the OP and in + * case of success it LOGs in the user. Otherwise an error message is put into the context and shown to the user + * afterwards. + * + * @param request the request object + * @param response the response object + * @param context the 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"); + + // verify the response + StringBuffer receivingURL = request.getRequestURL(); + String queryString = request.getQueryString(); + if (!StringUtils.isEmpty(queryString)) { + 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; + } + + // The current authentication mechanisms is implemented in a very restrictive manner, we have to + // retrieve the user password (which is generated randomly during registration for OpenID users) and use + // it in order to authenticate the user. + 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 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) { + if (rememberme != null) { + this.persistentLoginManager.rememberLogin(request, response, username, password); + } else { + this.persistentLoginManager.forgetLogin(request, response); + } + } + + request.setUserPrincipal(principal); + + String continueToURL = getContinueToURL(request); + response.sendRedirect(response.encodeRedirectURL(continueToURL)); + } + } else { + // authentication failed, show and LOG error message + 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. + * submits a LOGin request. * * @param request - * @return a URL to send the user to after logging in + * @return a URL to send the user to after LOGging in */ private String getContinueToURL(HttpServletRequest request) { Index: core/xwiki-core/src/main/resources/queries.hbm.xml =================================================================== --- core/xwiki-core/src/main/resources/queries.hbm.xml (revision 27619) +++ core/xwiki-core/src/main/resources/queries.hbm.xml (working copy) @@ -29,4 +29,7 @@ select distinct 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: core/xwiki-core/src/main/resources/ApplicationResources.properties =================================================================== --- core/xwiki-core/src/main/resources/ApplicationResources.properties (revision 27619) +++ core/xwiki-core/src/main/resources/ApplicationResources.properties (working copy) @@ -407,11 +407,59 @@ editFullScreen=Full Screen #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 invalidcredentials=Invalid credentials 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 +openid.common.discovery_cancelled=The login process was cancelled at the OpenID provider site +openid.common.discovery_failed=The discovery of the OpenID failed +openid.common.identifier_field_label=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.discovery_submit=Register with OpenID +openid.registration.confirmation=Verify the form below and complete your OpenID registration +openid.registration.confirmation_submit=Create OpenID account +openid.registration.invalidOpenID=Enter a valid OpenID to continue with registration +openid.registration.openIdAlreadyRegistered=The OpenID "{0}" is already registered. You can directly log in if it is in your possession. +openid.registration.successful=Successfully registered user {0} ({1}). + +#attach OpenID to existing user +openid.attach.title=Associate an OpenID with an existing user account +openid.attach.welcome=Enter the OpenID to attach to the user account. +openid.attach.confirm=Verify the data below and confirm the action to attach the shown OpenID to the user account. +openid.attach.user=User +openid.attach.submit=Attach OpenID +openid.attach.alreadyAttachedOpenId=This user account has already attached the OpenID "{0}". If you continue it will be replaced. +openid.attach.replaceAttachedOpenId=Replace the attached OpenID ({0}) +openid.attach.openIdAlreadyRegistered=The OpenID "{0}" is already registered with another user account. You can directly log in if it is in your possession. +openid.attach.internalError=An internal error occurred and the OpenID could not be associated with you user account. Please contact the administrator. +openid.attach.successful=Successfully attached the OpenID "{0}" to {1}. + +#OpenID server +openid.server.homepage.title=XWiki OpenID +openid.server.homepage.enabled_description=This is the homepage of XWiki's OpenID provider support. Every user can login on sites supporting OpenID without the need for yet another passwords. +openid.server.homepage.disabled_description=OpenID provider support is disabled in this installation. +openid.server.authentication_request.title=OpenID Authentication +openid.server.authentication_request.desc=Would you like to log in on the site
   {0}
with your OpenID
   {1} +openid.server.authentication_request.site_label=Site +openid.server.authentication_request.login=Login +openid.server.authentication_request.cancel=Cancel +openid.server.user_profile.title=OpenID profile of {0} +openid.server.user_profile.text=This is the OpenID profile page of {0}. + switchto=Switch to sectionEdit=Sectional Editing @@ -525,6 +573,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 @@ -587,6 +637,7 @@ platform.core.profile.changePassword=Change password platform.core.profile.changePhoto=Change photo platform.core.profile.changePhoto.cancel=Cancel and return to profile +platform.core.profile.attachOpenID=Attach an OpenID platform.core.profile.firstname=First name platform.core.profile.lastname=Last name platform.core.profile.blog=Blog @@ -704,6 +755,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 27619) +++ core/xwiki-core/src/main/resources/JcrQueries.properties (working copy) @@ -5,3 +5,4 @@ getAllPublicDocuments=//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/pom.xml =================================================================== --- core/xwiki-core/pom.xml (revision 27619) +++ core/xwiki-core/pom.xml (working copy) @@ -319,6 +319,13 @@ 2.0 + + + org.openid4java + openid4java + 0.9.3 + + dom4j @@ -1372,6 +1379,9 @@ **/web/Utils.java, **/web/TexAction.java, **/web/XWikiResponse.java, + **/web/OpenIdServerServlet.java, + **/plugin/openid/OpenIdHelper.java, + **/web/AttachOpenIdAction.java, **/atom/WSSEHttpHeader.java, **/atom/lifeblog/LifeblogServices.java, **/atom/lifeblog/UserBlog.java, Index: web/standard/src/main/webapp/WEB-INF/struts-config.xml =================================================================== --- web/standard/src/main/webapp/WEB-INF/struts-config.xml (revision 27623) +++ web/standard/src/main/webapp/WEB-INF/struts-config.xml (working copy) @@ -196,6 +196,13 @@ + + + + + + + + + + + + + + + OpenIdServer + com.xpn.xwiki.web.OpenIdServerServlet + + application + ApplicationResources + + + xwiki + com.xpn.xwiki.XWiki + + + RestletServlet @@ -261,7 +275,19 @@ /xwiki/* + + + + OpenIdServer + /openid/* + + + xmlrpc /xmlrpc/* Index: web/standard/src/main/webapp/resources/icons/xwiki/openid.png =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Property changes on: web/standard/src/main/webapp/resources/icons/xwiki/openid.png ___________________________________________________________________ Added: svn:mime-type + application/octet-stream Index: web/standard/src/main/webapp/templates/attach_openid.vm =================================================================== --- web/standard/src/main/webapp/templates/attach_openid.vm (revision 0) +++ web/standard/src/main/webapp/templates/attach_openid.vm (revision 0) @@ -0,0 +1,53 @@ +#template("startpage.vm") +
+ +

$msg.get("openid.attach.title")

+#if($status && $status <= 0) + #if($status == -1) + #warning("$msg.get('openid.attach.alreadyAttachedOpenId', [$current_openid_identifier])") + #elseif($status == -2) + #error("$msg.get('openid.registration.invalidOpenID')") + #elseif($status == -3) + #error("$msg.get('openid.attach.openIdAlreadyRegistered', [$openid_identifier])") + #elseif($status == -4) + #error("$msg.get('openid.common.discovery_cancelled')") + #elseif($status == -5) + #error("$msg.get('openid.common.discovery_failed')") + #else + #error("$msg.get('openid.attach.internalError')") + #end +#elseif($status && $status != 1) + #info("$msg.get('openid.attach.successful', [$openid_identifier, $username])") +#end +#if(!$status || ($status < -1)) +

$msg.get("openid.attach.welcome")

+
+
+ + +
+
$msg.get("openid.common.identifier_field_label")
+
+
+ +
+
+#elseif($status && ($status == 1 || $status == -1)) +

$msg.get("openid.attach.confirm")

+
+
+ + + +
+
$msg.get("openid.common.identifier_field_label")
+
$openid_identifier
+
$msg.get("openid.attach.user")
+
$username
+
+ +
+
+#end +
+#template("endpage.vm") \ No newline at end of file Index: web/standard/src/main/webapp/templates/login.vm =================================================================== --- web/standard/src/main/webapp/templates/login.vm (revision 27623) +++ web/standard/src/main/webapp/templates/login.vm (working copy) @@ -47,13 +47,29 @@ +
+ +
+
+ +

... or with your OpenID

+ + +
+
+ + +
+ ## TODO: Replace this with an interface extension once IX are implemented #if($xwiki.exists("XWiki.ResetPassword"))
Forgot your username or password?
#end #xwikimessageboxend() - ## mainContentArea ## main ## Ensure that the username field of the login form has the focus to make it easy for users to log in quickly Index: web/standard/src/main/webapp/templates/openid_server_homepage.vm =================================================================== --- web/standard/src/main/webapp/templates/openid_server_homepage.vm (revision 0) +++ web/standard/src/main/webapp/templates/openid_server_homepage.vm (revision 0) @@ -0,0 +1,11 @@ +#set($title = $msg.get("openid.server.homepage.title")) +#template("startpage.vm") +
+

$title

+#if($enabled) +

$msg.get("openid.server.homepage.enabled_description")

+#else +

$msg.get("openid.server.homepage.disabled_description")

+#end +
+#template("endpage.vm") \ No newline at end of file Index: web/standard/src/main/webapp/templates/openid_xrds.vm =================================================================== --- web/standard/src/main/webapp/templates/openid_xrds.vm (revision 0) +++ web/standard/src/main/webapp/templates/openid_xrds.vm (revision 0) @@ -0,0 +1,10 @@ + + + + + http://specs.openid.net/auth/2.0/server + $openid_server_url + + + Index: web/standard/src/main/webapp/templates/openid_user_xrds.vm =================================================================== --- web/standard/src/main/webapp/templates/openid_user_xrds.vm (revision 0) +++ web/standard/src/main/webapp/templates/openid_user_xrds.vm (revision 0) @@ -0,0 +1,23 @@ + + + + + http://specs.openid.net/auth/2.0/signon + $openid_server_url + $openid_identifier + + + http://openid.net/signon/1.1 + $openid_server_url + $openid_identifier + + + http://openid.net/signon/1.0 + $openid_server_url + $openid_identifier + + + Index: web/standard/src/main/webapp/templates/confirm_openid_authentication_request.vm =================================================================== --- web/standard/src/main/webapp/templates/confirm_openid_authentication_request.vm (revision 0) +++ web/standard/src/main/webapp/templates/confirm_openid_authentication_request.vm (revision 0) @@ -0,0 +1,13 @@ +#template("startpage.vm") +
+

$msg.get("openid.server.authentication_request.title")

+

$msg.get("openid.server.authentication_request.desc", [$site, $openid_identifier])

+
+
+ + + +
+
+
+#template("endpage.vm") \ No newline at end of file Index: web/standard/src/main/webapp/templates/registerinline.vm =================================================================== --- web/standard/src/main/webapp/templates/registerinline.vm (revision 27623) +++ web/standard/src/main/webapp/templates/registerinline.vm (working copy) @@ -9,16 +9,22 @@ #error($msg.get('core.register.passwordMismatch')) #elseif($reg == -3) #error($msg.get('core.register.userAlreadyExists')) + #elseif($reg== -13) + #error("$msg.get('openid.registration.openIdAlreadyRegistered', [$openid_identifier])") #elseif($reg == -4) #error($msg.get('core.register.invalidUsername')) + #elseif($reg==-14) + #error("$msg.get('openid.registration.invalidOpenID')") #elseif($reg == -8) #error($msg.get('core.register.userAlreadyExists')) #else #error($msg.get('core.register.registerFailed', [$reg])) #end - #elseif($reg) + #elseif($reg && $reg != 2 && $reg != 3) #set($xwname = "XWiki.${request.xwikiname}") #info($msg.get('core.register.successful', [$xwiki.getUserName($xwname), $request.xwikiname])) + #elseif($reg && $reg == 3) + #info("$msg.get('openid.registration.successful', [$username, $openid_identifier])") #end #if(!$reg || $reg < 0)
@@ -61,7 +67,56 @@
- #end +

$msg.get("openid.registration.welcome")

+
+
+ + + #set($class = $xwiki.getClass("XWiki.XWikiUsers")) + #set($obj = $class.newObject()) + #set($serverobj = $class.newObject()) + #set($discard = $doc.use("XWiki.XWikiUsers")) +
+
$msg.get("openid.common.identifier_field_label")
+
+
+ +
+
+ #elseif($reg && $reg == 2) +

$msg.get("openid.registration.confirmation")

+
+
+ + + + #set($class = $xwiki.getClass("XWiki.XWikiUsers")) + #set($obj = $class.newObject()) + #set($serverobj = $class.newObject()) + #set($discard = $doc.use("XWiki.XWikiUsers")) + #if($first_name) + $obj.set("first_name", $first_name) + #end + #if($last_name) + $obj.set("last_name", $last_name) + #end +
+
$msg.get("openid.common.identifier_field_label")
+
$openid_identifier
+ #set($prop = $class.first_name) +
$msg.get("core.register.firstName")
+
$doc.displayEdit($prop, "register_", $obj)
+ #set($prop = $class.last_name) +
$msg.get("core.register.lastName")
+
$doc.displayEdit($prop, "register_", $obj)
+ #set($prop = $class.email) +
$msg.get("core.register.email")
+
$doc.displayEdit($prop, "register_", $obj)
+
+ +
+
+ #end #else ## An override exists in the wiki, display it #set($doc = $xwiki.getDocument('XWiki.Registration')) Index: web/standard/src/main/webapp/templates/openid_form_redirect.vm =================================================================== --- web/standard/src/main/webapp/templates/openid_form_redirect.vm (revision 0) +++ web/standard/src/main/webapp/templates/openid_form_redirect.vm (revision 0) @@ -0,0 +1,16 @@ + + + + $msg.get("openid.form_redirect.title") + + +

$msg.get("openid.form_redirect.description")

+
+ #foreach($parameter_key in $openid_parameters.keySet()) + + #end + +
+ + Index: web/standard/src/main/webapp/templates/openid_user_profile.vm =================================================================== --- web/standard/src/main/webapp/templates/openid_user_profile.vm (revision 0) +++ web/standard/src/main/webapp/templates/openid_user_profile.vm (revision 0) @@ -0,0 +1,7 @@ +#set($title = $msg.get("openid.server.user_profile.title")) +#template("startpage.vm") +
+

$title

+

$msg.get("openid.server.user_profile.text", [$username])

+
+#template("endpage.vm") \ No newline at end of file Index: applications/administration/src/main/resources/XWiki/XWikiUserSheet.xml =================================================================== --- applications/administration/src/main/resources/XWiki/XWikiUserSheet.xml (revision 27534) +++ applications/administration/src/main/resources/XWiki/XWikiUserSheet.xml (working copy) @@ -499,6 +499,10 @@ #end </ul> </div> + <div id="openId"> + <span id="attachOpenId" class="hidden">$msg.get('platform.core.profile.attachOpenID')</span> + <a href="$doc.getURL("attachopenid")" ><img class="openid" alt="$msg.get('platform.core.profile.attachOpenID')" src="$xwiki.getSkinFile('icons/openid.png')"/></a> + </div> </div> ## ## Panes @@ -516,4 +520,4 @@ </div> {{/html}} {{/velocity}} - \ No newline at end of file +