diff --git a/app/src/main/java/org/apache/roller/weblogger/webservices/xmlrpc/BaseAPIHandler.java b/app/src/main/java/org/apache/roller/weblogger/webservices/xmlrpc/BaseAPIHandler.java index 642692415..1edf402b0 100644 --- a/app/src/main/java/org/apache/roller/weblogger/webservices/xmlrpc/BaseAPIHandler.java +++ b/app/src/main/java/org/apache/roller/weblogger/webservices/xmlrpc/BaseAPIHandler.java @@ -28,9 +28,12 @@ import org.apache.roller.weblogger.config.WebloggerRuntimeConfig; import org.apache.roller.weblogger.business.WebloggerFactory; import org.apache.roller.weblogger.business.UserManager; +import org.apache.roller.weblogger.business.WeblogEntryManager; import org.apache.roller.weblogger.business.WeblogManager; import org.apache.roller.weblogger.pojos.User; import org.apache.roller.weblogger.pojos.Weblog; +import org.apache.roller.weblogger.pojos.WeblogEntry; +import org.apache.roller.weblogger.pojos.WeblogPermission; import org.apache.roller.weblogger.ui.core.RollerContext; import org.apache.roller.weblogger.util.cache.CacheManager; import org.apache.xmlrpc.common.XmlRpcNotAuthorizedException; @@ -102,53 +105,29 @@ public BaseAPIHandler() { //------------------------------------------------------------------------ /** - * Returns website, but only if user authenticates and is authorized to edit. - * @param blogid Blogid sent in request (used as website's handle) - * @param username Username sent in request - * @param password Password sent in request + * Returns a weblog only when the authenticated user has the requested + * permission and XML-RPC access is enabled for that weblog. */ - protected Weblog validate(String blogid, String username, String password) - throws Exception { - boolean authenticated = false; - boolean userEnabled = false; - boolean weblogEnabled = false; - boolean apiEnabled = false; - boolean weblogFound = false; - Weblog website = null; - try { - UserManager userMgr = WebloggerFactory.getWeblogger().getUserManager(); - WeblogManager weblogMgr = WebloggerFactory.getWeblogger().getWeblogManager(); - User user = userMgr.getUserByUserName(username); - - website = weblogMgr.getWeblogByHandle(blogid); - if (website != null) { - weblogFound = true; - weblogEnabled = website.getVisible(); - apiEnabled = website.getEnableBloggerApi() - && WebloggerRuntimeConfig.getBooleanProperty("webservices.enableXmlRpc"); - } - - if (user != null) { - userEnabled = user.getEnabled(); - authenticated = RollerContext.getPasswordEncoder().matches(password, user.getPassword()); - } - } catch (Exception e) { - mLogger.error("ERROR internal error validating user", e); - } - - if ( !authenticated ) { - throw new XmlRpcNotAuthorizedException(AUTHORIZATION_EXCEPTION_MSG); - } - if ( !userEnabled ) { - throw new XmlRpcNotAuthorizedException(USER_DISABLED_MSG); - } - if ( !weblogEnabled ) { + protected Weblog validate(String blogid, String username, String password, + String requiredAction) throws Exception { + User user = validateUser(username, password); + return validateWeblog(blogid, user, requiredAction); + } + + /** + * Validate a weblog for an already authenticated user. + */ + protected Weblog validateWeblog(String blogid, User user, + String requiredAction) throws Exception { + WeblogManager weblogMgr = WebloggerFactory.getWeblogger().getWeblogManager(); + Weblog website = weblogMgr.getWeblogByHandle(blogid); + + // Use one response for missing, unavailable, and inaccessible weblogs. + if (!isWeblogAvailable(website) + || !website.hasUserPermission(user, requiredAction)) { throw new XmlRpcNotAuthorizedException(WEBLOG_DISABLED_MSG); } - if ( !weblogFound ) { - throw new XmlRpcException(WEBLOG_NOT_FOUND, WEBLOG_NOT_FOUND_MSG); - } - if ( !apiEnabled ) { + if (!Boolean.TRUE.equals(website.getEnableBloggerApi())) { throw new XmlRpcNotAuthorizedException(BLOGGERAPI_DISABLED_MSG); } return website; @@ -156,43 +135,82 @@ protected Weblog validate(String blogid, String username, String password) //------------------------------------------------------------------------ /** - * Returns true if username/password are valid and user is not disabled. + * Returns the authenticated user if username/password are valid and the + * user is not disabled. * @param username Username sent in request * @param password Password sent in request */ - protected boolean validateUser(String username, String password) - throws Exception { + protected User validateUser(String username, String password) + throws Exception { + User user = null; boolean authenticated = false; - boolean enabled = false; - boolean apiEnabled = false; try { - UserManager userMgr = WebloggerFactory.getWeblogger().getUserManager(); - User user = userMgr.getUserByUserName(username); - - if (user != null) { - enabled = user.getEnabled(); - authenticated = RollerContext.getPasswordEncoder().matches(password, user.getPassword()); - - apiEnabled = WebloggerRuntimeConfig.getBooleanProperty("webservices.enableXmlRpc"); + user = userMgr.getUserByUserName(username); + if (user != null && RollerContext.getPasswordEncoder() != null) { + authenticated = RollerContext.getPasswordEncoder().matches( + password, user.getPassword()); } } catch (Exception e) { mLogger.error("ERROR internal error validating user", e); } - - if ( !authenticated ) { + + if (!authenticated) { throw new XmlRpcNotAuthorizedException(AUTHORIZATION_EXCEPTION_MSG); } - - if ( !enabled ) { + + if (!Boolean.TRUE.equals(user.getEnabled())) { throw new XmlRpcNotAuthorizedException(USER_DISABLED_MSG); } - - if ( !apiEnabled ) { + + if (!WebloggerRuntimeConfig.getBooleanProperty("webservices.enableXmlRpc")) { throw new XmlRpcNotAuthorizedException(BLOGGERAPI_DISABLED_MSG); - } - - return authenticated; + } + + return user; + } + + /** + * Returns an entry only when it belongs to an available XML-RPC weblog and + * the user may edit it. An optional additional weblog action can be + * required for transitions such as publishing. + */ + protected WeblogEntry validateEntry(String postid, User user, + String additionalAction) throws Exception { + WeblogEntry entry = getEntryForWrite(postid, user, additionalAction); + if (entry == null) { + throw new XmlRpcException(INVALID_POSTID, INVALID_POSTID_MSG); + } + return entry; + } + + /** + * Nullable form used by Blogger.deletePost(), whose public contract + * returns false when the entry is unavailable. + */ + protected WeblogEntry getEntryForWrite(String postid, User user, + String additionalAction) throws Exception { + WeblogEntryManager entryMgr = WebloggerFactory.getWeblogger() + .getWeblogEntryManager(); + WeblogEntry entry = entryMgr.getWeblogEntry(postid); + if (entry == null || !isWeblogAvailable(entry.getWebsite()) + || !Boolean.TRUE.equals(entry.getWebsite().getEnableBloggerApi()) + || !entry.getWebsite().hasUserPermission( + user, WeblogPermission.EDIT_DRAFT) + || !entry.hasWritePermissions(user)) { + return null; + } + if (additionalAction != null + && !entry.getWebsite().hasUserPermission(user, additionalAction)) { + return null; + } + return entry; + } + + private boolean isWeblogAvailable(Weblog website) { + return website != null + && Boolean.TRUE.equals(website.getVisible()) + && Boolean.TRUE.equals(website.getActive()); } //------------------------------------------------------------------------ diff --git a/app/src/main/java/org/apache/roller/weblogger/webservices/xmlrpc/BloggerAPIHandler.java b/app/src/main/java/org/apache/roller/weblogger/webservices/xmlrpc/BloggerAPIHandler.java index ee7d40ab0..dea3aaccf 100644 --- a/app/src/main/java/org/apache/roller/weblogger/webservices/xmlrpc/BloggerAPIHandler.java +++ b/app/src/main/java/org/apache/roller/weblogger/webservices/xmlrpc/BloggerAPIHandler.java @@ -33,7 +33,6 @@ import org.apache.roller.weblogger.WebloggerException; import org.apache.roller.weblogger.business.Weblogger; import org.apache.roller.weblogger.business.WebloggerFactory; -import org.apache.roller.weblogger.business.UserManager; import org.apache.roller.weblogger.business.WeblogEntryManager; import org.apache.roller.weblogger.pojos.CustomTemplateRendition; import org.apache.roller.weblogger.pojos.TemplateRendition.RenditionType; @@ -43,6 +42,8 @@ import org.apache.roller.weblogger.pojos.WeblogEntrySearchCriteria; import org.apache.roller.weblogger.pojos.WeblogTemplate; import org.apache.roller.weblogger.pojos.Weblog; +import org.apache.roller.weblogger.pojos.WeblogPermission; +import org.apache.roller.weblogger.pojos.ThemeTemplate.ComponentType; import org.apache.roller.weblogger.util.Utilities; import org.apache.xmlrpc.XmlRpcException; @@ -59,7 +60,10 @@ public class BloggerAPIHandler extends BaseAPIHandler { static final long serialVersionUID = 2398898776655115019L; private static Log mLogger = LogFactory.getLog(BloggerAPIHandler.class); - + + /** Upper bound on the entry scan a limited (non-POST) member can trigger. */ + protected static final int DRAFT_SCAN_CAP = 200; + public BloggerAPIHandler() { super(); } @@ -86,15 +90,14 @@ public boolean deletePost(String appkey, String postid, String userid, Weblogger roller = WebloggerFactory.getWeblogger(); WeblogEntryManager weblogMgr = roller.getWeblogEntryManager(); - WeblogEntry entry = weblogMgr.getWeblogEntry(postid); - - // Return false if entry not found + User user = validateUser(userid, password); + WeblogEntry entry = getEntryForWrite(postid, user, null); + + // Return false if entry is not available to this user. if (entry == null) { return false; } - validate(entry.getWebsite().getHandle(), userid, password); - try { // notify cache flushPageCache(entry.getWebsite()); @@ -138,7 +141,8 @@ public boolean setTemplate(String appkey, String blogid, String userid, mLogger.debug(" Template: " + templateData); mLogger.debug(" Type: " + templateType); - validate(blogid, userid, password); + Weblog website = validate(blogid, userid, password, + WeblogPermission.ADMIN); if (!templateType.equals("main")) { throw new XmlRpcException( @@ -146,7 +150,13 @@ public boolean setTemplate(String appkey, String blogid, String userid, } try { - WeblogTemplate template = WebloggerFactory.getWeblogger().getWeblogManager().getTemplate(templateType); + WeblogTemplate template = WebloggerFactory.getWeblogger() + .getWeblogManager().getTemplateByAction( + website, ComponentType.WEBLOG); + if (template == null) { + throw new XmlRpcException(UNKNOWN_EXCEPTION, + "Standard rendering of template not found"); + } CustomTemplateRendition ctr = template.getTemplateRendition(RenditionType.STANDARD); if (ctr != null) { ctr.setTemplate(templateData); @@ -186,11 +196,19 @@ public String getTemplate(String appkey, String blogid, String userid, mLogger.debug(" UserId: " + userid); mLogger.debug(" Type: " + templateType); - validate(blogid, userid,password); + Weblog website = validate(blogid, userid, password, + WeblogPermission.ADMIN); try { CustomTemplateRendition ctr = null; - WeblogTemplate template = WebloggerFactory.getWeblogger().getWeblogManager().getTemplate(templateType); + WeblogTemplate template; + if ("main".equals(templateType)) { + template = WebloggerFactory.getWeblogger().getWeblogManager() + .getTemplateByAction(website, ComponentType.WEBLOG); + } else { + template = WebloggerFactory.getWeblogger().getWeblogManager() + .getTemplateByName(website, templateType); + } if (template != null) { ctr = template.getTemplateRendition(RenditionType.STANDARD); } @@ -223,43 +241,33 @@ public Object getUserInfo(String appkey, String userid, String password) mLogger.debug(" Appkey: " + appkey); mLogger.debug(" UserId: " + userid); - validateUser(userid, password); + User user = validateUser(userid, password); - try { - Weblogger roller = WebloggerFactory.getWeblogger(); - UserManager userMgr = roller.getUserManager(); - User user = userMgr.getUserByUserName(userid); - - // parses full name into two strings, firstname and lastname - String firstname = "", lastname = ""; - StringTokenizer toker = new StringTokenizer(user.getFullName()); - - if (toker.hasMoreTokens()) { - firstname = toker.nextToken(); - } - - while (toker.hasMoreTokens()) { - if ( !lastname.isEmpty() ) { - lastname += " "; - } - lastname += toker.nextToken(); - } + // parses full name into two strings, firstname and lastname + String firstname = "", lastname = ""; + StringTokenizer toker = new StringTokenizer(user.getFullName()); - // TODO: Should screen name be renamed nickname and used here? - // populates user information to return as a result - Hashtable result = new Hashtable<>(); - result.put("nickname", user.getUserName()); - result.put("userid", user.getUserName()); - result.put("email", ""); - result.put("lastname", lastname); - result.put("firstname", firstname); - - return result; - } catch (WebloggerException e) { - String msg = "ERROR in BlooggerAPIHander.getInfo"; - mLogger.error(msg,e); - throw new XmlRpcException(UNKNOWN_EXCEPTION,msg); + if (toker.hasMoreTokens()) { + firstname = toker.nextToken(); + } + + while (toker.hasMoreTokens()) { + if (!lastname.isEmpty()) { + lastname += " "; + } + lastname += toker.nextToken(); } + + // TODO: Should screen name be renamed nickname and used here? + // populates user information to return as a result + Hashtable result = new Hashtable<>(); + result.put("nickname", user.getUserName()); + result.put("userid", user.getUserName()); + result.put("email", ""); + result.put("lastname", lastname); + result.put("firstname", firstname); + + return result; } @@ -280,28 +288,25 @@ public Object getUsersBlogs(String appkey, String userid, String password) mLogger.debug(" UserId: " + userid); Vector result = new Vector<>(); - if (validateUser(userid, password)) { - try { - UserManager umgr = WebloggerFactory.getWeblogger().getUserManager(); - User user = umgr.getUserByUserName(userid); - - // get list of user's enabled websites - List websites = WebloggerFactory.getWeblogger().getWeblogManager().getUserWeblogs(user, true); - for (Weblog website : websites) { - // only include weblog's that have client API support enabled - if (Boolean.TRUE.equals(website.getEnableBloggerApi())) { - Hashtable blog = new Hashtable<>(3); - blog.put("url", website.getURL()); - blog.put("blogid", website.getHandle()); - blog.put("blogName", website.getName()); - result.add(blog); - } + User user = validateUser(userid, password); + try { + // get list of user's enabled websites + List websites = WebloggerFactory.getWeblogger() + .getWeblogManager().getUserWeblogs(user, true); + for (Weblog website : websites) { + // only include weblogs that have client API support enabled + if (Boolean.TRUE.equals(website.getEnableBloggerApi())) { + Hashtable blog = new Hashtable<>(3); + blog.put("url", website.getURL()); + blog.put("blogid", website.getHandle()); + blog.put("blogName", website.getName()); + result.add(blog); } - } catch (Exception e) { - String msg = "ERROR in BlooggerAPIHander.getUsersBlogs"; - mLogger.error(msg,e); - throw new XmlRpcException(UNKNOWN_EXCEPTION, msg); } + } catch (Exception e) { + String msg = "ERROR in BlooggerAPIHander.getUsersBlogs"; + mLogger.error(msg,e); + throw new XmlRpcException(UNKNOWN_EXCEPTION, msg); } return result; } @@ -330,36 +335,35 @@ public boolean editPost(String appkey, String postid, String userid, mLogger.debug(" Publish: " + publish); mLogger.debug(" Content:\n " + content); - if (validateUser(userid, password)) { - try { - Timestamp current = new Timestamp(System.currentTimeMillis()); - - Weblogger roller = WebloggerFactory.getWeblogger(); - WeblogEntryManager weblogMgr = roller.getWeblogEntryManager(); - WeblogEntry entry = weblogMgr.getWeblogEntry(postid); - entry.setText(content); - entry.setUpdateTime(current); - if (publish) { - entry.setStatus(PubStatus.PUBLISHED); - } else { - entry.setStatus(PubStatus.DRAFT); - } - - // save the entry - weblogMgr.saveWeblogEntry(entry); - roller.flush(); - - // notify cache - flushPageCache(entry.getWebsite()); - - return true; - } catch (Exception e) { - String msg = "ERROR in BlooggerAPIHander.editPost"; - mLogger.error(msg,e); - throw new XmlRpcException(UNKNOWN_EXCEPTION, msg); + User user = validateUser(userid, password); + WeblogEntry entry = validateEntry(postid, user, + publish ? WeblogPermission.POST : null); + try { + Timestamp current = new Timestamp(System.currentTimeMillis()); + + Weblogger roller = WebloggerFactory.getWeblogger(); + WeblogEntryManager weblogMgr = roller.getWeblogEntryManager(); + entry.setText(content); + entry.setUpdateTime(current); + if (publish) { + entry.setStatus(PubStatus.PUBLISHED); + } else { + entry.setStatus(PubStatus.DRAFT); } + + // save the entry + weblogMgr.saveWeblogEntry(entry); + roller.flush(); + + // notify cache + flushPageCache(entry.getWebsite()); + + return true; + } catch (Exception e) { + String msg = "ERROR in BlooggerAPIHander.editPost"; + mLogger.error(msg,e); + throw new XmlRpcException(UNKNOWN_EXCEPTION, msg); } - return false; } @@ -386,7 +390,9 @@ public String newPost(String appkey, String blogid, String userid, mLogger.debug(" Publish: " + publish); mLogger.debug(" Content:\n " + content); - Weblog website = validate(blogid, userid, password); + User user = validateUser(userid, password); + Weblog website = validateWeblog(blogid, user, + publish ? WeblogPermission.POST : WeblogPermission.EDIT_DRAFT); // extract the title from the content String title = ""; @@ -413,7 +419,6 @@ public String newPost(String appkey, String blogid, String userid, entry.setLocale(website.getLocale()); entry.setPubTime(current); entry.setUpdateTime(current); - User user = roller.getUserManager().getUserByUserName(userid); entry.setCreatorUserName(user.getUserName()); entry.setWebsite(website); entry.setCategory(website.getBloggerCategory()); @@ -463,10 +468,15 @@ public Object getRecentPosts(String appkey, String blogid, String userid, mLogger.debug(" UserId: " + userid); mLogger.debug(" Number: " + numposts); - Weblog weblog = validate(blogid, userid,password); + User user = validateUser(userid, password); + Weblog weblog = validateWeblog(blogid, user, + WeblogPermission.EDIT_DRAFT); try { Vector results = new Vector<>(); + if (numposts <= 0) { + return results; + } Weblogger roller = WebloggerFactory.getWeblogger(); WeblogEntryManager weblogMgr = roller.getWeblogEntryManager(); @@ -474,10 +484,19 @@ public Object getRecentPosts(String appkey, String blogid, String userid, WeblogEntrySearchCriteria wesc = new WeblogEntrySearchCriteria(); wesc.setWeblog(weblog); wesc.setEndDate(new Date()); + if (weblog.hasUserPermission(user, WeblogPermission.POST)) { + wesc.setMaxResults(numposts); + } else { + wesc.setMaxResults(Math.max(numposts, DRAFT_SCAN_CAP)); + } Map> entries = weblogMgr.getWeblogEntryObjectMap(wesc); + outer: for (List weList : entries.values()) { for (WeblogEntry entry : weList) { + if (!entry.hasWritePermissions(user)) { + continue; + } Hashtable result = new Hashtable<>(); if (entry.getPubTime() != null) { result.put("dateCreated", entry.getPubTime()); @@ -486,6 +505,9 @@ public Object getRecentPosts(String appkey, String blogid, String userid, result.put("postid", entry.getId()); result.put("content", entry.getText()); results.add(result); + if (results.size() >= numposts) { + break outer; + } } } } diff --git a/app/src/main/java/org/apache/roller/weblogger/webservices/xmlrpc/MetaWeblogAPIHandler.java b/app/src/main/java/org/apache/roller/weblogger/webservices/xmlrpc/MetaWeblogAPIHandler.java index dc8ce3f69..98b5764db 100644 --- a/app/src/main/java/org/apache/roller/weblogger/webservices/xmlrpc/MetaWeblogAPIHandler.java +++ b/app/src/main/java/org/apache/roller/weblogger/webservices/xmlrpc/MetaWeblogAPIHandler.java @@ -43,6 +43,7 @@ import org.apache.roller.weblogger.pojos.WeblogEntry; import org.apache.roller.weblogger.pojos.WeblogEntry.PubStatus; import org.apache.roller.weblogger.pojos.WeblogEntrySearchCriteria; +import org.apache.roller.weblogger.pojos.WeblogPermission; import org.apache.roller.weblogger.util.RollerMessages; import org.apache.roller.weblogger.util.Utilities; import org.apache.xmlrpc.XmlRpcException; @@ -82,7 +83,8 @@ public Object getCategories(String blogid, String userid, String password) mLogger.debug(" BlogId: " + blogid); mLogger.debug(" UserId: " + userid); - Weblog website = validate(blogid, userid,password); + Weblog website = validate(blogid, userid, password, + WeblogPermission.EDIT_DRAFT); Weblogger roller = WebloggerFactory.getWeblogger(); try { Hashtable result = new Hashtable<>(); @@ -129,9 +131,9 @@ public boolean editPost(String postid, String userid, String password, Weblogger roller = WebloggerFactory.getWeblogger(); WeblogEntryManager weblogMgr = roller.getWeblogEntryManager(); - WeblogEntry entry = weblogMgr.getWeblogEntry(postid); - - validate(entry.getWebsite().getHandle(), userid,password); + User user = validateUser(userid, password); + WeblogEntry entry = validateEntry(postid, user, + publish ? WeblogPermission.POST : null); Hashtable postcontent = struct; String description = (String)postcontent.get("description"); @@ -227,7 +229,9 @@ public String newPost(String blogid, String userid, String password, mLogger.debug(" UserId: " + userid); mLogger.debug(" Publish: " + publish); - Weblog website = validate(blogid, userid, password); + User user = validateUser(userid, password); + Weblog website = validateWeblog(blogid, user, + publish ? WeblogPermission.POST : WeblogPermission.EDIT_DRAFT); Hashtable postcontent = struct; String description = (String)postcontent.get("description"); @@ -252,7 +256,6 @@ public String newPost(String blogid, String userid, String password, try { Weblogger roller = WebloggerFactory.getWeblogger(); WeblogEntryManager weblogMgr = roller.getWeblogEntryManager(); - User user = roller.getUserManager().getUserByUserName(userid); Timestamp current = new Timestamp(System.currentTimeMillis()); WeblogEntry entry = new WeblogEntry(); @@ -330,14 +333,8 @@ public Object getPost(String postid, String userid, String password) mLogger.debug(" PostId: " + postid); mLogger.debug(" UserId: " + userid); - Weblogger roller = WebloggerFactory.getWeblogger(); - WeblogEntryManager weblogMgr = roller.getWeblogEntryManager(); - WeblogEntry entry = weblogMgr.getWeblogEntry(postid); - - if (entry == null) { - throw new XmlRpcException(INVALID_POSTID, INVALID_POSTID_MSG); - } - validate(entry.getWebsite().getHandle(), userid, password); + User user = validateUser(userid, password); + WeblogEntry entry = validateEntry(postid, user, null); try { return createPostStruct(entry, userid); @@ -362,7 +359,8 @@ public Object newMediaObject(String blogid, String userid, String password, mLogger.debug(" UserId: " + userid); mLogger.debug(" Password: *********"); - Weblog website = validate(blogid, userid, password); + Weblog website = validate(blogid, userid, password, + WeblogPermission.POST); try { String name = (String) struct.get("name"); name = name.replace("/","_"); @@ -425,10 +423,15 @@ public Object getRecentPosts(String blogid, String userid, String password, mLogger.debug(" UserId: " + userid); mLogger.debug(" Number: " + numposts); - Weblog website = validate(blogid, userid,password); + User user = validateUser(userid, password); + Weblog website = validateWeblog(blogid, user, + WeblogPermission.EDIT_DRAFT); try { Vector results = new Vector<>(); + if (numposts <= 0) { + return results; + } Weblogger roller = WebloggerFactory.getWeblogger(); WeblogEntryManager weblogMgr = roller.getWeblogEntryManager(); @@ -436,11 +439,21 @@ public Object getRecentPosts(String blogid, String userid, String password, WeblogEntrySearchCriteria wesc = new WeblogEntrySearchCriteria(); wesc.setWeblog(website); wesc.setSortBy(WeblogEntrySearchCriteria.SortBy.UPDATE_TIME); - wesc.setMaxResults(numposts); + if (website.hasUserPermission(user, WeblogPermission.POST)) { + wesc.setMaxResults(numposts); + } else { + wesc.setMaxResults(Math.max(numposts, DRAFT_SCAN_CAP)); + } List entries = weblogMgr.getWeblogEntries(wesc); for (WeblogEntry entry : entries) { + if (!entry.hasWritePermissions(user)) { + continue; + } results.addElement(createPostStruct(entry, userid)); + if (results.size() >= numposts) { + break; + } } } return results; diff --git a/app/src/test/java/org/apache/roller/weblogger/webservices/xmlrpc/XMLRPCWeblogPermissionTest.java b/app/src/test/java/org/apache/roller/weblogger/webservices/xmlrpc/XMLRPCWeblogPermissionTest.java new file mode 100644 index 000000000..6dce4978a --- /dev/null +++ b/app/src/test/java/org/apache/roller/weblogger/webservices/xmlrpc/XMLRPCWeblogPermissionTest.java @@ -0,0 +1,440 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.roller.weblogger.webservices.xmlrpc; + +import java.lang.reflect.Field; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.List; +import java.util.Map; +import java.util.Vector; + +import org.apache.roller.weblogger.TestUtils; +import org.apache.roller.weblogger.business.PropertiesManager; +import org.apache.roller.weblogger.business.UserManager; +import org.apache.roller.weblogger.business.WeblogManager; +import org.apache.roller.weblogger.business.Weblogger; +import org.apache.roller.weblogger.business.WebloggerFactory; +import org.apache.roller.weblogger.pojos.CustomTemplateRendition; +import org.apache.roller.weblogger.pojos.RuntimeConfigProperty; +import org.apache.roller.weblogger.pojos.TemplateRendition.RenditionType; +import org.apache.roller.weblogger.pojos.TemplateRendition.TemplateLanguage; +import org.apache.roller.weblogger.pojos.ThemeTemplate.ComponentType; +import org.apache.roller.weblogger.pojos.User; +import org.apache.roller.weblogger.pojos.Weblog; +import org.apache.roller.weblogger.pojos.WeblogEntry; +import org.apache.roller.weblogger.pojos.WeblogEntry.PubStatus; +import org.apache.roller.weblogger.pojos.WeblogPermission; +import org.apache.roller.weblogger.pojos.WeblogTemplate; +import org.apache.roller.weblogger.ui.core.RollerContext; +import org.apache.xmlrpc.XmlRpcException; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.security.crypto.password.DelegatingPasswordEncoder; +import org.springframework.security.crypto.password.NoOpPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Integration coverage for the Blogger and MetaWeblog permission matrix. + */ +public class XMLRPCWeblogPermissionTest { + + private static final String PASSWORD = "password"; + + private final BloggerAPIHandler blogger = new BloggerAPIHandler(); + private final MetaWeblogAPIHandler metaWeblog = new MetaWeblogAPIHandler(); + + private User owner; + private User author; + private User limited; + private User outsider; + private Weblog weblog; + private Weblog otherWeblog; + private WeblogTemplate otherTemplate; + private WeblogEntry draftEntry; + private WeblogEntry secondDraftEntry; + private WeblogEntry publishedEntry; + private WeblogEntry deleteEntry; + private String oldXmlRpcValue; + private Object oldPasswordEncoder; + + @BeforeEach + public void setUp() throws Exception { + TestUtils.setupWeblogger(); + installTestPasswordEncoder(); + + Weblogger roller = WebloggerFactory.getWeblogger(); + PropertiesManager propertiesManager = roller.getPropertiesManager(); + RuntimeConfigProperty xmlRpcProperty = propertiesManager + .getProperty("webservices.enableXmlRpc"); + oldXmlRpcValue = xmlRpcProperty.getValue(); + xmlRpcProperty.setValue("true"); + propertiesManager.saveProperty(xmlRpcProperty); + + owner = TestUtils.setupUser("xmlrpcOwner"); + author = TestUtils.setupUser("xmlrpcAuthor"); + limited = TestUtils.setupUser("xmlrpcLimited"); + outsider = TestUtils.setupUser("xmlrpcOutsider"); + + weblog = TestUtils.setupWeblog("xmlrpcPermissionWeblog", owner); + otherWeblog = TestUtils.setupWeblog("xmlrpcOtherWeblog", owner); + enableXmlRpc(weblog); + enableXmlRpc(otherWeblog); + + UserManager userManager = roller.getUserManager(); + userManager.grantWeblogPermission(weblog, author, + Collections.singletonList(WeblogPermission.POST)); + userManager.grantWeblogPermission(weblog, limited, + Collections.singletonList(WeblogPermission.EDIT_DRAFT)); + + createMainTemplate(weblog, "main-template-code"); + otherTemplate = createMainTemplate(otherWeblog, "other-template-code"); + TestUtils.endSession(true); + + draftEntry = TestUtils.setupWeblogEntry("xmlrpc-draft", + weblog.getWeblogCategories().iterator().next(), PubStatus.DRAFT, + weblog, owner); + secondDraftEntry = TestUtils.setupWeblogEntry("xmlrpc-second-draft", + weblog.getWeblogCategories().iterator().next(), PubStatus.DRAFT, + weblog, owner); + publishedEntry = TestUtils.setupWeblogEntry("xmlrpc-published", + weblog.getWeblogCategories().iterator().next(), PubStatus.PUBLISHED, + weblog, owner); + deleteEntry = TestUtils.setupWeblogEntry("xmlrpc-delete", + weblog.getWeblogCategories().iterator().next(), PubStatus.DRAFT, + weblog, owner); + TestUtils.endSession(true); + } + + @AfterEach + public void tearDown() throws Exception { + TestUtils.teardownWeblog(weblog.getId()); + TestUtils.teardownWeblog(otherWeblog.getId()); + TestUtils.teardownUser(owner.getUserName()); + TestUtils.teardownUser(author.getUserName()); + TestUtils.teardownUser(limited.getUserName()); + TestUtils.teardownUser(outsider.getUserName()); + + PropertiesManager propertiesManager = WebloggerFactory.getWeblogger() + .getPropertiesManager(); + RuntimeConfigProperty xmlRpcProperty = propertiesManager + .getProperty("webservices.enableXmlRpc"); + xmlRpcProperty.setValue(oldXmlRpcValue); + propertiesManager.saveProperty(xmlRpcProperty); + TestUtils.endSession(true); + restorePasswordEncoder(); + } + + @Test + public void testAuthenticationAndWeblogDiscovery() throws Exception { + @SuppressWarnings("unchecked") + Hashtable userInfo = (Hashtable) blogger + .getUserInfo("", outsider.getUserName(), PASSWORD); + assertEquals(outsider.getUserName(), userInfo.get("userid")); + + @SuppressWarnings("unchecked") + Vector ownerBlogs = (Vector) blogger.getUsersBlogs("", + owner.getUserName(), PASSWORD); + assertEquals(2, ownerBlogs.size()); + + Weblog managedOtherWeblog = WebloggerFactory.getWeblogger() + .getWeblogManager().getWeblog(otherWeblog.getId()); + managedOtherWeblog.setEnableBloggerApi(false); + WebloggerFactory.getWeblogger().getWeblogManager() + .saveWeblog(managedOtherWeblog); + TestUtils.endSession(true); + + @SuppressWarnings("unchecked") + Vector enabledOwnerBlogs = (Vector) blogger.getUsersBlogs( + "", owner.getUserName(), PASSWORD); + assertEquals(1, enabledOwnerBlogs.size()); + assertEquals(weblog.getHandle(), postField(enabledOwnerBlogs.get(0), + "blogid")); + + @SuppressWarnings("unchecked") + Vector outsiderBlogs = (Vector) blogger.getUsersBlogs("", + outsider.getUserName(), PASSWORD); + assertTrue(outsiderBlogs.isEmpty()); + + assertThrows(XmlRpcException.class, () -> blogger.getUserInfo("", + outsider.getUserName(), "wrong-password")); + + User managedOutsider = WebloggerFactory.getWeblogger().getUserManager() + .getUserByUserName(outsider.getUserName()); + managedOutsider.setEnabled(false); + WebloggerFactory.getWeblogger().getUserManager().saveUser(managedOutsider); + TestUtils.endSession(true); + assertThrows(XmlRpcException.class, () -> blogger.getUserInfo("", + outsider.getUserName(), PASSWORD)); + } + + @Test + public void testGlobalXmlRpcSettingIsRequired() throws Exception { + PropertiesManager propertiesManager = WebloggerFactory.getWeblogger() + .getPropertiesManager(); + RuntimeConfigProperty xmlRpcProperty = propertiesManager + .getProperty("webservices.enableXmlRpc"); + xmlRpcProperty.setValue("false"); + propertiesManager.saveProperty(xmlRpcProperty); + + assertThrows(XmlRpcException.class, () -> blogger.getUserInfo("", + owner.getUserName(), PASSWORD)); + } + + @Test + public void testWeblogOperationPermissionMatrix() throws Exception { + String ownerName = owner.getUserName(); + String authorName = author.getUserName(); + String limitedName = limited.getUserName(); + String outsiderName = outsider.getUserName(); + String handle = weblog.getHandle(); + + assertEquals("main-template-code", blogger.getTemplate("", handle, + ownerName, PASSWORD, "main")); + assertTrue(blogger.setTemplate("", handle, ownerName, PASSWORD, + "updated-template-code", "main")); + assertEquals("updated-template-code", blogger.getTemplate("", handle, + ownerName, PASSWORD, "main")); + assertThrows(XmlRpcException.class, () -> blogger.getTemplate("", handle, + authorName, PASSWORD, "main")); + assertThrows(XmlRpcException.class, () -> blogger.setTemplate("", handle, + authorName, PASSWORD, "unapproved", "main")); + + // Supplying another weblog's template ID must not escape the requested weblog. + XmlRpcException foreignTemplate = assertThrows(XmlRpcException.class, + () -> blogger.getTemplate("", handle, ownerName, PASSWORD, + otherTemplate.getId())); + XmlRpcException missingTemplate = assertThrows(XmlRpcException.class, + () -> blogger.getTemplate("", handle, ownerName, PASSWORD, + "missing-template")); + assertEquals(missingTemplate.code, foreignTemplate.code); + assertEquals(missingTemplate.getMessage(), foreignTemplate.getMessage()); + + assertNotNull(metaWeblog.getCategories(handle, limitedName, PASSWORD)); + assertThrows(XmlRpcException.class, () -> metaWeblog.getCategories(handle, + outsiderName, PASSWORD)); + + assertNotNull(blogger.newPost("", handle, limitedName, PASSWORD, + "draft from limited member", false)); + assertThrows(XmlRpcException.class, () -> blogger.newPost("", handle, + limitedName, PASSWORD, "published by limited member", true)); + assertNotNull(blogger.newPost("", handle, authorName, PASSWORD, + "published by author", true)); + + Hashtable draft = postStruct("meta draft"); + assertNotNull(metaWeblog.newPost(handle, limitedName, PASSWORD, draft, + false)); + assertThrows(XmlRpcException.class, () -> metaWeblog.newPost(handle, + limitedName, PASSWORD, postStruct("meta published"), true)); + + // Invalid media input stops after authorization without writing a file. + Hashtable media = new Hashtable<>(); + XmlRpcException authorMediaError = assertThrows(XmlRpcException.class, + () -> metaWeblog.newMediaObject(handle, authorName, PASSWORD, + media)); + assertEquals(BaseAPIHandler.UNKNOWN_EXCEPTION, authorMediaError.code); + XmlRpcException limitedMediaError = assertThrows(XmlRpcException.class, + () -> metaWeblog.newMediaObject(handle, limitedName, PASSWORD, + media)); + assertTrue(limitedMediaError.code != BaseAPIHandler.UNKNOWN_EXCEPTION); + } + + @Test + public void testEntryOperationPermissionMatrix() throws Exception { + String authorName = author.getUserName(); + String limitedName = limited.getUserName(); + String outsiderName = outsider.getUserName(); + + assertTrue(blogger.editPost("", draftEntry.getId(), limitedName, + PASSWORD, "limited draft edit", false)); + assertThrows(XmlRpcException.class, () -> blogger.editPost("", + secondDraftEntry.getId(), limitedName, PASSWORD, + "limited publish", true)); + assertTrue(blogger.editPost("", secondDraftEntry.getId(), authorName, + PASSWORD, "author publish", true)); + assertThrows(XmlRpcException.class, () -> blogger.editPost("", + draftEntry.getId(), outsiderName, PASSWORD, "foreign edit", false)); + + assertNotNull(metaWeblog.getPost(draftEntry.getId(), limitedName, PASSWORD)); + assertThrows(XmlRpcException.class, () -> metaWeblog.getPost( + publishedEntry.getId(), limitedName, PASSWORD)); + assertNotNull(metaWeblog.getPost(publishedEntry.getId(), authorName, + PASSWORD)); + + assertTrue(metaWeblog.editPost(draftEntry.getId(), limitedName, PASSWORD, + postStruct("limited meta edit"), false)); + assertThrows(XmlRpcException.class, () -> metaWeblog.editPost( + draftEntry.getId(), limitedName, PASSWORD, + postStruct("limited meta publish"), true)); + + assertFalse(blogger.deletePost("", publishedEntry.getId(), outsiderName, + PASSWORD, false)); + assertTrue(blogger.deletePost("", deleteEntry.getId(), limitedName, + PASSWORD, false)); + + XmlRpcException missing = assertThrows(XmlRpcException.class, + () -> metaWeblog.getPost("missing-entry", outsiderName, PASSWORD)); + XmlRpcException inaccessible = assertThrows(XmlRpcException.class, + () -> metaWeblog.getPost(publishedEntry.getId(), outsiderName, + PASSWORD)); + assertEquals(missing.code, inaccessible.code); + assertEquals(missing.getMessage(), inaccessible.getMessage()); + } + + @Test + public void testRecentPostsAreFilteredForLimitedMembers() throws Exception { + String handle = weblog.getHandle(); + String limitedName = limited.getUserName(); + String authorName = author.getUserName(); + + @SuppressWarnings("unchecked") + Vector bloggerLimited = (Vector) blogger.getRecentPosts( + "", handle, limitedName, PASSWORD, 50); + assertContainsPost(bloggerLimited, draftEntry.getId()); + assertDoesNotContainPost(bloggerLimited, publishedEntry.getId()); + + @SuppressWarnings("unchecked") + Vector metaLimited = (Vector) metaWeblog.getRecentPosts( + handle, limitedName, PASSWORD, 50); + assertContainsPost(metaLimited, draftEntry.getId()); + assertDoesNotContainPost(metaLimited, publishedEntry.getId()); + + @SuppressWarnings("unchecked") + Vector metaAuthor = (Vector) metaWeblog.getRecentPosts( + handle, authorName, PASSWORD, 50); + assertContainsPost(metaAuthor, publishedEntry.getId()); + + assertThrows(XmlRpcException.class, () -> blogger.getRecentPosts("", + handle, outsider.getUserName(), PASSWORD, 50)); + assertThrows(XmlRpcException.class, () -> metaWeblog.getRecentPosts( + handle, outsider.getUserName(), PASSWORD, 50)); + } + + @Test + public void testDisabledWeblogAndApiAreRejected() throws Exception { + WeblogManager weblogManager = WebloggerFactory.getWeblogger() + .getWeblogManager(); + Weblog managedWeblog = weblogManager.getWeblog(weblog.getId()); + managedWeblog.setEnableBloggerApi(false); + weblogManager.saveWeblog(managedWeblog); + TestUtils.endSession(true); + + assertThrows(XmlRpcException.class, () -> metaWeblog.getCategories( + weblog.getHandle(), owner.getUserName(), PASSWORD)); + + managedWeblog = WebloggerFactory.getWeblogger().getWeblogManager() + .getWeblog(weblog.getId()); + managedWeblog.setVisible(true); + managedWeblog.setActive(false); + WebloggerFactory.getWeblogger().getWeblogManager() + .saveWeblog(managedWeblog); + TestUtils.endSession(true); + + assertThrows(XmlRpcException.class, () -> metaWeblog.getCategories( + weblog.getHandle(), owner.getUserName(), PASSWORD)); + + managedWeblog = WebloggerFactory.getWeblogger().getWeblogManager() + .getWeblog(weblog.getId()); + managedWeblog.setEnableBloggerApi(true); + managedWeblog.setVisible(false); + WebloggerFactory.getWeblogger().getWeblogManager() + .saveWeblog(managedWeblog); + TestUtils.endSession(true); + + assertThrows(XmlRpcException.class, () -> metaWeblog.getCategories( + weblog.getHandle(), owner.getUserName(), PASSWORD)); + } + + private void enableXmlRpc(Weblog target) throws Exception { + target.setEnableBloggerApi(true); + WebloggerFactory.getWeblogger().getWeblogManager().saveWeblog(target); + } + + @SuppressWarnings("deprecation") + private void installTestPasswordEncoder() throws Exception { + Field encoderField = RollerContext.class.getDeclaredField("encoder"); + encoderField.setAccessible(true); + oldPasswordEncoder = encoderField.get(null); + + PasswordEncoder noOp = NoOpPasswordEncoder.getInstance(); + Map encoders = new HashMap<>(); + encoders.put(null, noOp); + encoders.put("noop", noOp); + encoderField.set(null, new DelegatingPasswordEncoder("noop", encoders)); + } + + private void restorePasswordEncoder() throws Exception { + Field encoderField = RollerContext.class.getDeclaredField("encoder"); + encoderField.setAccessible(true); + encoderField.set(null, oldPasswordEncoder); + } + + private WeblogTemplate createMainTemplate(Weblog target, String code) + throws Exception { + WeblogTemplate template = new WeblogTemplate(); + template.setAction(ComponentType.WEBLOG); + template.setName("Weblog"); + template.setDescription("Test weblog template"); + template.setLink("Weblog"); + template.setLastModified(new Date()); + template.setWeblog(target); + + WeblogManager weblogManager = WebloggerFactory.getWeblogger() + .getWeblogManager(); + weblogManager.saveTemplate(template); + CustomTemplateRendition rendition = new CustomTemplateRendition( + template, RenditionType.STANDARD); + rendition.setTemplate(code); + rendition.setTemplateLanguage(TemplateLanguage.VELOCITY); + weblogManager.saveTemplateRendition(rendition); + return template; + } + + private Hashtable postStruct(String title) { + Hashtable struct = new Hashtable<>(); + struct.put("title", title); + struct.put("description", title + " body"); + return struct; + } + + private void assertContainsPost(List posts, String postId) { + assertTrue(posts.stream().map(this::postId).anyMatch(postId::equals)); + } + + private void assertDoesNotContainPost(List posts, String postId) { + assertFalse(posts.stream().map(this::postId).anyMatch(postId::equals)); + } + + @SuppressWarnings("unchecked") + private String postId(Object post) { + return ((Hashtable) post).get("postid").toString(); + } + + @SuppressWarnings("unchecked") + private String postField(Object post, String field) { + return ((Hashtable) post).get(field); + } +}