We have a SharePoint Web Application that has a number of Site Collections underneath 2 different managed paths (depts & offices) e.g
http://sharepoint.abc/depts/finance
http://sharepoint.abc/depts/isg
http://sharepoint.abc/offices/boston
http://sharepoint.abc/offices/chicago
When a user logs in they are presented with a list of the site collections they have read access to using the following c# code which is in the WebPart
SPSecurity.RunWithElevatedPrivileges(delegate()
{
using (SPSite spSite = new SPSite(SPContext.Current.Site.Url))
{
foreach (SPSite site in spSite.WebApplication.Sites)
{
try
{
var rootWeb = site.RootWeb;
if (rootWeb.DoesUserHavePermissions(SPContext.Current.Web.CurrentUser.LoginName, SPBasePermissions.ViewPages))
{
if (this.ValidSite(rootWeb.Url))
{
string url = GetRelativePath(rootWeb.Url);
allowedSites.Add(new SiteInfo(rootWeb.Title, url));
}
}
}
catch (Exception ex)
{
this.Controls.Add(new LiteralControl("<br/>GetAllowedSites Error: " + ex.Message));
}
}
}
});
It works fine but in production it takes 20-seconds to load the webpart (we have 700 site collections across the 2 paths).
I've used caching to hold the list of their sites but once the cache expires it takes 20-seconds to regenerate itself.
Ideally what I want is to see what Site Collections a user can access using the User rather than iterating through all the Site Collections to see if the user has access to them. Can this be achieved???
Thanks
eaigs
Try to use SPWeb.GetSubwebsForCurrentUser method to get the subsites beneath the current website of which the current user is a member.
Related
I am going crazy since two days solving an issue. The problem is;
I am making a console APP which is talking to SharePoint Online using global admin account (One which was specified as admin while making a new subscription). What I am trying to achieve is, I want to add a custom action using CSOM to each site collection and subsite of office 365. That code works fine except on the root site collection which is pre-created by office 365 while signing up (i.e. https://xyz.sharepoint.com)
For any tenant for root site collection, it gives me below error;
{
"SchemaVersion":"15.0.0.0","LibraryVersion":"16.0.3912.1201","ErrorInfo":{
"ErrorMessage":"Access denied. You do not have permission to perform
this action or access this
resource.","ErrorValue":null,"TraceCorrelationId":"2a47fd9c-c07b-1000-cfb7-cdffbe3ab83a","ErrorCode":-2147024891,"ErrorTypeName":"System.UnauthorizedAccessException"
},"TraceCorrelationId":"2a47fd9c-c07b-1000-cfb7-cdffbe3ab83a" }
Now the user is global admin. I also added again that user as site collection admin.
The same piece of code works fine on other site collections (search site collection, any newly made site collection...).
here is a code;
using (ClientContext spcollContext = new ClientContext(web.Url))
{
SecureString passWord = new SecureString();
foreach (char c in strAdminPassword.ToCharArray()) passWord.AppendChar(c);
SharePointOnlineCredentials creds = new SharePointOnlineCredentials(strAdminUser, passWord);
spcollContext.Credentials = creds;
Web currentweb = spcollContext.Web;
spcollContext.Load(currentweb);
spcollContext.ExecuteQuery();
// authCookie = creds.GetAuthenticationCookie(new Uri(web.Url));
var existingActions2 = currentweb.UserCustomActions;
spcollContext.Load(existingActions2);
spcollContext.ExecuteQuery();
var actions2 = existingActions2.ToArray();
foreach (var action in actions2)
{
if (action.Description == "CustomScriptCodeForEachsite" &&
action.Location == "ScriptLink")
{
action.DeleteObject();
spcollContext.ExecuteQuery();
}
}
var newAction2 = existingActions2.Add();
newAction2.Description = "CustomScriptCodeForEachsite";
newAction2.Location = "ScriptLink";
newAction2.ScriptBlock = scriptBlock;
newAction2.Update();
spcollContext.Load(currentweb, s => s.UserCustomActions);
spcollContext.ExecuteQuery(); // GETTING ERROR ON THIS LINE.
}
Note: Above error is Fiddler traces.
Most probably this behavior is caused by Custom Script feature, basically
the issue occurs when the Custom Script feature is turned off
How to verify?
You could verify the site permissions using the following console app:
using (var ctx = GetContext(webUri, userName, password))
{
var rootWeb = ctx.Site.RootWeb;
ctx.Load(rootWeb, w => w.EffectiveBasePermissions);
ctx.ExecuteQuery();
var permissions = rootWeb.EffectiveBasePermissions;
foreach (var permission in Enum.GetValues(typeof(PermissionKind)).Cast<PermissionKind>())
{
var permissionName = Enum.GetName(typeof(PermissionKind), permission);
var hasPermission = permissions.Has(permission);
Console.WriteLine("Permission: {0}, HasPermission: {1}", permissionName, hasPermission);
}
}
where
public static ClientContext GetContext(Uri webUri, string userName, string password)
{
var securePassword = new SecureString();
foreach (var ch in password) securePassword.AppendChar(ch);
return new ClientContext(webUri) {Credentials = new SharePointOnlineCredentials(userName, securePassword)};
}
When SP.PermissionKind.AddAndCustomizePages is set to False, the Access denied error occurs while adding user custom action.
Solution
According to Turn scripting capabilities on or off:
For self-service created sites, custom scripting is disabled by
default
Solution: enable Allow users to run custom scripts on self-service created sites
To enable or disable scripting from the SharePoint admin center
Sign in to Office 365 with your work or school account.
Go to the SharePoint admin center.
Select Settings.
Under Custom Script choose:
Prevent users from running custom script on personal sites or Allow
users to run custom script on personal sites.
Prevent users from running custom script on user created sites or
Allow users to run custom script on self-service created sites.
Select OK. It takes about 24 hours for the change to take
effect.
Since any change to the scripting setting made through the SharePoint Online admin center may take up to 24 hours to take effect, you could enable scripting on a particular site collection immediately via CSOM API (SharePoint Online Client Components SDK) as demonstrated below:
public static void DisableDenyAddAndCustomizePages(ClientContext ctx, string siteUrl)
{
var tenant = new Tenant(ctx);
var siteProperties = tenant.GetSitePropertiesByUrl(siteUrl, true);
ctx.Load(siteProperties);
ctx.ExecuteQuery();
siteProperties.DenyAddAndCustomizePages = DenyAddAndCustomizePagesStatus.Disabled;
var result = siteProperties.Update();
ctx.Load(result);
ctx.ExecuteQuery();
while (!result.IsComplete)
{
Thread.Sleep(result.PollingInterval);
ctx.Load(result);
ctx.ExecuteQuery();
}
}
Usage
using (var ctx = GetContext(webUri, userName, password))
{
using (var tenantAdminCtx = GetContext(tenantAdminUri, userName, password))
{
DisableDenyAddAndCustomizePages(tenantAdminCtx,webUri.ToString());
}
RegisterJQueryLibrary(ctx);
}
where
public static void RegisterJQueryLibrary(ClientContext context)
{
var actions = context.Site.UserCustomActions;
var action = actions.Add();
action.Location = "ScriptLink";
action.ScriptSrc = "~SiteCollection/Style Library/Scripts/jQuery/jquery.min.js";
action.Sequence = 1482;
action.Update();
context.ExecuteQuery();
}
If you don't have time for CSOM as described by Vadim, the page also links to a powershell script you can use:
Set-SPOsite <SiteURL> -DenyAddAndCustomizePages 0
But note that SiteUrl needs to be the admin url. If your tenant is https://mysite.sharepoint.com, the url you use is https://mysite-admin.sharepoint.com"
In our case, we were in the midst of a deployment when this hit and could not wait 24 hours (or even one hour!) to continue. Everything had been fine in our testing site collections, but when we deployed to the tenant root, we hit the error described above and this script fixed it. Apparently the feature is turned off by default on the tenant root.
Current site is not a tenant administration site
Turn scripting capabilities on or off
My first response would be that you shouldn't add a CustomAction on the fly through code. That said, I'm sure you have a good reason to need to do so.
Try to set the AllowUnsafeUpdates flag on SPWeb to true as soon as you reference currentWeb. Make sure to also set it back to false after you call the final ExecuteQuery()
By default, AllowUnsafeUpdates is false. It is used to block cross-site scripting attacks.
https://msdn.microsoft.com/en-us/library/Microsoft.SharePoint.SPWeb_properties.aspx
I am running my code under administrative rights that iterates over different site->spweb to check the existance of a list. Strangely and for some site the code returns me that list does not exists(exception : List does not exists) Yet i can totally browse the list from browser on the same web
what am i missing here ?
EDIT:Adding Code
foreach (SPSite s in webApp.Sites)
{
foreach (SPWeb w in s.AllWebs)
{
try{
SPList sourceList = w.Lists["Ticks and Cross"];
}catch(exception ex){ ..... }
}
s.Dispose();
}
All sites has same templates, so there is no chance that list is not ther. For some site i get the sourceList, for other its exception, its really wired
I guess you pass incorrect parameter to Lists[]. You should specify valid guid, title or index of the list. Can you post your code?
I am iterating through my Site Collections in SharePoint for a custom navigation. But I am trying to only allow the iteration to output nodes from one level deep after /sites/. For example, sites/IT.
At the moment, my method is iterating through all nodes. For example, sites/IT/Support.
private void GetSiteChildNodes(string siteName)
{
SPSecurity.RunWithElevatedPrivileges(delegate()
{
foreach (SPSite site in SPContext.Current.Site.WebApplication.Sites)
{
try
{
if (SPSite.Exists(new Uri(site.Url)) && site.ServerRelativeUrl.StartsWith(String.Format("/sites/{0}/", siteName)))
{
SPWeb subSites = site.RootWeb;
foreach (SPWeb cn in subSites.Webs)
{
navBuilder.AppendFormat("<li>{1}</li>", cn.Url, cn.Title);
}
}
}
finally
{
site.Dispose();
}
}
});
}
As you can see from my code, I am using "RootWeb" so that I ignore any child nodes from within the site. But that is not working.
Any help would be appreciated.
Try this:
using (SPSite oSiteCollection = new SPSite("http://<>"))
{
SPWeb web = oSiteCollection.OpenWeb("sites");
foreach (SPWeb oWebsite in web.Webs)
{
Console.WriteLine("Web site: {0}", oWebsite.Url);
oWebsite.Dispose();
}
}
Upon reading your comment, #R100, if you have managed paths defined for your subsites, then each managed path (no matter the logical hierarchy you put into place) is at the same level in the SPWebApplication.Sites collection.
For example, if you have the following hierarchy:
Root (/) [Site Collection]
Sites (/sites) [Site Collection]
IT (/sites/IT) [Site Collection]
Support (/sites/IT/Support) [Site Collection]
then SPWebApplication.Sites will contain each of those above in a flattened collection.
Additionally, the ServerRelativeUrl for site collections is all relative to the root of the SharePoint installation. Thus, what you see in the parenthesis in the above hierarchy is the ServerRelativeUrl for each of the nodes.
Thus, your check when you're iterating over your values is true for each child site collection under /sites/it (if it is the siteName you are passing down):
site.ServerRelativeUrl.StartsWith(String.Format("/sites/{0}/", siteName))
From code I've automatically created a lot of similar sites (SPWeb) in my site collection from a site template (in Sharepoint Foundation). Every site has a home page on which I've added the "what's new" web part (found under "Social collaboration").
Even though the web part has several "target lists" (I'd have called it "source lists") added to it on the template site, this connection is lost on the sites created from the template. So I need to programmatically find all these web parts and add the target lists to them. Looping the web parts is not an issue - I've done that before - but I can't seem to find a word on the net on how to go about modifying this particular web part. All I have is a brief intellisense.
I've found out that it recides in the
Microsoft.SharePoint.Applications.GroupBoard.WebPartPages
namespace, but on the lists provided on MSDN this is one of very few namespaces that doesn't have a link to a reference documentation.
Does anyone have any experience of modifying this web part from code? If not, how would you go about to find out? I can't seem to figure out a method for this..
Here is how I did it. It worked really well. I had a feature that created several list instances and provisioned the What's New web part. In the Feature Receiver, I looped through all of the list instances, indexed the Modified field, and then added the list to the web part:
private void ConfigureLists(SPWeb web, SPFeatureReceiverProperties properties)
{
List<Guid> ids = new List<Guid>();
SPElementDefinitionCollection elements =
properties.Feature.Definition.GetElementDefinitions(new CultureInfo((int)web.Language, false));
foreach (SPElementDefinition element in elements)
{
if ("ListInstance" == element.ElementType)
{
XmlNode node = element.XmlDefinition;
SPList list = web.Lists[node.Attributes["Title"].Value];
SPField field = list.Fields[SPBuiltInFieldId.Modified];
if (!field.Indexed)
{
field.Indexed = true;
field.Update();
}
ids.Add(list.ID);
}
}
string targetConfig = string.Empty;
foreach (Guid id in ids)
{
targetConfig += string.Format("'{0}',''\n", id);
}
SPFile file = web.GetFile("Pages/default.aspx");
file.CheckOut();
using (SPLimitedWebPartManager manager = file.GetLimitedWebPartManager(PersonalizationScope.Shared))
{
WhatsNewWebPart webpart = null;
foreach (System.Web.UI.WebControls.WebParts.WebPart eachWebPart in manager.WebParts)
{
webpart = eachWebPart as WhatsNewWebPart;
if (null != webpart)
{
break;
}
}
if (null != webpart)
{
webpart.TargetConfig = targetConfig;
manager.SaveChanges(webpart);
}
}
file.CheckIn("ConfigureWebParts");
file.Publish("ConfigureWebParts");
file.Approve("ConfigureWebParts");
}
If you are unsure about the property, export the web part from the browser, then open the .webpart/.dwp file with a text editor. Somewhere in the xml will be a reference to the source list.
*.webparts are usually easier to modify, just set the property.
*.dwps are harder because you sometimes have to get the property (eg ViewXML), then load it into an XmlDocument, then replace the property, and write the xml document string value back to ViewXML.
Is there a way to get a site's (site collection) site directory, the one which is defined within the web site collection setting of a collaboration or publishing portal?
Of course I could iterate through all the SPWebs of the site collection but I hope there's a easier way to get the directory as the information seems to be already stored somewhere.
Bye,
Flo
UPDATE
Of course I want to get the information programmatically.
The site directory path is stored in the root web property bag
The code below taken from an internal utility class SiteDirectoryUtil shows the logic of how to determine the path
internal static string GetLocalSiteDirectoryLocation(SPWeb rootWeb)
{
if (rootWeb != null)
{
if (rootWeb.AllProperties.ContainsKey("DefaultSiteDirectorySiteId"))
{
string str = rootWeb.AllProperties["DefaultSiteDirectorySiteId"] as string;
if (string.IsNullOrEmpty(str))
{
return string.Empty;
}
if (!rootWeb.AllProperties.ContainsKey("DefaultSiteDirectoryWebId"))
{
return string.Empty;
}
string str2 = rootWeb.AllProperties["DefaultSiteDirectoryWebId"] as string;
if (string.IsNullOrEmpty(str2))
{
return string.Empty;
}
try
{
using (SPSite site = new SPSite(new Guid(str), rootWeb.Site.Zone))
{
using (SPWeb web = site.OpenWeb(new Guid(str2)))
{
return web.ServerRelativeUrl;
}
}
}
catch (FileNotFoundException)
{
return string.Empty;
}
}
else
{
return string.Empty;
}
}
return string.Empty;
}
Are you referring to the site directory that is under the Site Content and Structure?
If so, you can browse to http://sitename/_layouts/sitemanager.aspx to see site directory (assuming you have sufficient privileges to the site).
For what it's worth, if you have access to the WSS database(s) you can either leverage off existing SPs or create your own. I wanted to return things like 'RequestAccessEmail' and incorporate that into our own site maps (handy to know who the site owner is sometimes). Our SP at it's most basic...
CREATE PROCEDURE [dbo].[my_sitemap](
#PartialUrl nvarchar(260)
)
AS
SELECT ID, FullUrl, Title, RequestAccessEmail
FROM Webs
WHERE FullUrl LIKE #PartialUrl + '%'
ORDER BY FullUrl
GO
Worked very well for me. Hope this helps.