SharePoint 2007: GetWebCollection (Webs.asmx) does not return webs based on a user permission
Supposedly in Webs.asmx web service we pass on the user credentials and then call GetWebCollection method to get the webs underneath the current site, we expect the web method to return only those webs for which the user has permission to. But this web method actually returns all the webs present underneath the current site.
Let us assume that there are 2 users, User1 and User2. User1 is the site owner and User2 is added to the site collection members group. There is a root site in the site collection and a sub-site (say site1) underneath the site collection. Both the users has access to the root site and the sub-site. Then in the sub-site, site1, we go to Advanced permission settings and remove the permission for the members group. When User1 logs in, he will be able to see both the site collection and the sub-site (site1) as he has permission in both the sites. When User2 logs in, he will be able to see only the root site and not the sub-site ( as his permission is removed in the sub-site). This works perfectly fine in the UI.
When the same is executed via web service passing User2 credentials, the web method returns all the sub-sites. In this case it returns sub-site site1 as well ( though he doesn't have permission on that site). So irrespective of the permission, the web method returns all the sub-sites. The web method initially checks if the credential passed has permission on the site collection level ( in this case the User2 has permission ) and then returns all the sub-sites.
localhost.Webs web = new global::Webs.localhost.Webs();
web.Credentials = new System.Net.NetworkCredential("User2", "test123!", "chnatara2k8");
string sXML = web.GetWebCollection().OuterXml;
Reason for this behavior? Actually the Web Service uses Object Model to return all the Sub Webs, essentially it is the same as SPWeb.Webs call. One important thing is SPWeb.Webs returns all subwebs and it doesn't matter whether the current user has permission or not.
Workaround using SharePoint Object Model - In the SharePoint object model, there is a method called GetSubwebsForCurrentUser, to get the sub-sites specific to a user. So, we can create a custom web service to get subwebs based on a user.
Sample Code:
SPSite site1 = new SPSite("https://chnatara2k8:31666");
SPWeb web = site1.OpenWeb();
SPUser user = web.AllUsers["chnatara2k8\\user2"]; // User Name
SPUserToken token = user.UserToken;
SPSite site = new SPSite("https://chnatara2k8:31666", token);
SPWeb web2 = site.OpenWeb();
SPWebCollection webCollection = web2.GetSubwebsForCurrentUser();
Another possible workaround is to use UserGroup web service. This web service returns all the users for a site. Once we get the list of users for a site, then we can filter down to check if a user has access to the site or not. It's a intricate process to get subwebs for a particular user. For this we also need to create a web service proxy instance.
Sample Code:
private void button1_Click(object sender, EventArgs e)
{
localhost.Webs web = new global::Webs.localhost.Webs();
web.Credentials = new System.Net.NetworkCredential("user1", "test123!", "chnatara2k8");
string oSubSite = "";
System.Xml.XmlNode nd = web.GetWebCollection();
XmlNodeList ndlist = nd.ChildNodes;
foreach (XmlNode xGet in ndlist)
{
string sWebTitle = xGet.Attributes["Title"].Value;
oSubSite = xGet.Attributes["Url"].Value;
string myUrl = oSubSite;
Uri myUri = new Uri(myUrl);
UserGroup.UserGroup ug = Form1.CreateWebService<UserGroup.UserGroup>(myUri);
XmlNode ugweb = ug.GetUserCollectionFromSite();
XmlNodeList ugndlist = ugweb.ChildNodes[0].ChildNodes;
foreach (XmlNode xNode in ugndlist)
{
string oUser = xNode.Attributes["LoginName"].Value;
//Add it in a collection for filtering purpose.
}
}
}
public static WSType CreateWebService<WSType>(Uri siteUrl) where WSType : SoapHttpClientProtocol, new()
{
WSType webService = new WSType();
webService.Credentials = System.Net.CredentialCache.DefaultNetworkCredentials;
string webServiceName = typeof(WSType).Name;
webService.Url = string.Format("{0}/_vti_bin/{1}.asmx", siteUrl, webServiceName);
return webService;
}