SecurityException using SPWebConfigModification - sharepoint

I'm getting a SecurityException, "Access Denied" when trying to make a web.config modification programmatically.
Code closely follows this example:
public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
// create modification
SPSecurity.RunWithElevatedPrivileges(delegate()
{
SPWebConfigModification m = new SPWebConfigModification();
m.Path = "configuration/SharePoint/SafeControls";
m.Name = string.Format(CultureInfo.InvariantCulture, "SafeControl[#Assembly='{0}'][#Namespace='{1}'][#TypeName='*'][#Safe='True']", ADSWebPart.GetAssemblyFullName(), ADSWebPart.GetNamespace());
m.Sequence = 0;
m.Owner = SPContext.Current.Web.CurrentUser.Name;
m.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode;
m.Value = string.Format(CultureInfo.InvariantCulture, "<SafeControl Assembly='{0}' Namespace='{1}' TypeName='*' Safe='True' />", ADSWebPart.GetAssemblyFullName(), ADSWebPart.GetNamespace());
// apply modification
SPWebService service = SPWebService.ContentService;
service.WebConfigModifications.Add(m);
service.Update();
service.ApplyWebConfigModifications();
});
}
(I started with no call to RunWithElevatedPrivileges(), got same exception, then continued enclosing more and more code up to enclosing all body of FeatureActivated().)
Ideas welcome, thanks.

I'm not answering your question as asked, but why are you using a Feature receiver to add SafeControl Entries?
The "Right" way to add SafeControl Entries is to embed these inside <SafeControls> inside the corresponding <Assembly> in manifest.xml of your Solution like this:
<Assembly Location="MyLib.dll" DeploymentTarget="WebApplication">
<SafeControls>
<SafeControl Assembly="MyLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=4489c7aa5341e32c" Namespace="MyNs" TypeName="*" Safe="True" />
</SafeControls>
</Assembly>

Out of sheer curiosity, is the web.config set to read only? Have you checked the permissions on the filesystem for the web directory in inetpub?
Also, is this being activated on the Web Application level or further down?

Instead of using SPContext.Current.Web.CurrentUser.Name;
Try following the instructions here to impersonate the SystemAccount:
http://blackninjasoftware.com/2009/04/09/how-to-programmatically-impersonate-users-in-sharepoint/

Another question, are you activating the feature from a Forms Based Authentication site? You can have issues because the site collection administrator is from the authentication provider path, and not AD. The Web.Config file security is based on the AD account, so you have no ability to update the web.config without doing something bad, like giving Everyone access to web.config.
Here is an earlier question I posted on the topic.
Modifying SharePoint app Web.config file with Forms Based Authentication

Related

How Can I have IIS properly serve .webmanifest files on my web site?

The Favicon Generator assembles a package for webmasters to use in order to have icons available for many different devices. The page comes with a file called site.manifest which is linked to via the following tag in the web page's document <head>:
<link rel="manifest" href="site.webmanifest">
According to Mozilla: "The web app manifest provides information about an application (such as name, author, icon, and description) in a JSON text file. The purpose of the manifest is to install web applications to the homescreen of a device, providing users with quicker access and a richer experience."
Unfortunately if you are using Microsoft's Internet Information Services (IIS), you'll get a 404.3 error if you try and access the site.webmanifest file.
The exact error message is as follows: "The page you are requesting cannot be served because of the extension configuration. If the page is a script, add a handler. If the file should be downloaded, add a MIME map."
How can I properly serve site.webmanifest files in IIS?
By default, IIS does not serve any files that does not have a MIME map associated with it in its (IIS) core settings.
To address this challenge, you will need to map the .webmanifest file extension to its appropriate MIME type.
To accomplish this, open IIS and follow the steps below;
On the left hand side, select either your web site or the entire server in the "Connections" menu.
If you select the server, your MIME mapping will apply to every web site on the server.
If you select a web site, it will only apply to a single web site.
Next, select "MIME Types" from the IIS menu:
Once there, click "add..." from the right hand menu.
In the dialog box that opens specify .webmanifest in the file name extension box application/manifest+json in the MIME type box.
Click "OK".
Congratulations; you've just defined the MIME type for .webmanifest on IIS.
For Azure I added this as the web.config
<?xml version="1.0"?>
<configuration>
<system.webServer>
<staticContent>
<mimeMap fileExtension=".json" mimeType="application/json" />
<mimeMap fileExtension=".webmanifest" mimeType="application/manifest+json" />
</staticContent>
</system.webServer>
</configuration>
For those using ASP.NET Core (I am using 2.1) you can configure the MIME types that can be served in the application Startup.cs file as per the static files docs:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
FileExtensionContentTypeProvider provider = new FileExtensionContentTypeProvider();
provider.Mappings[".webmanifest"] = "application/manifest+json";
app.UseStaticFiles(new StaticFileOptions()
{
ContentTypeProvider = provider
});
app.UseMvc();
}
Easier solution is to rename your manifest file to site.webmanifest.json and link as
<link rel="manifest" href="site.webmanifest.json">
IIS should already have a MIME Type for .json files
This is also helpful if deploying to Azure where its not so easy to change the IIS settings.
Adding to #Ben's answer: if you have a SPA you should put StaticFileOptions code into the UseSpaStaticFiles() call:
FileExtensionContentTypeProvider provider = new FileExtensionContentTypeProvider();
provider.Mappings[".webmanifest"] = "application/manifest+json";
app.UseSpaStaticFiles(new StaticFileOptions()
{
ContentTypeProvider = provider
});
I found that the IIS server had ".json" listed in the Request Filtering feature saying it was not allowed.
Removing that allowed the file to be served.

Stop Sharing Cookies between Applications under same Site ID in IIS

The issue I have is we currently are using IdentityServer as our SSO authentication for our corporate applications. However, the bulk of our applications are under the same Site ID in IIS 7.5. When navigating to more than 5 of these applications under the same Site ID, you end up getting a 400 error, request header too long. The reason being each application has its own cookie, so the request header is passing around 5+ cookies with token information and the becoming too large.
My question is, are you able to prevent the sharing of cookies between applications under the same Site ID in IIS 7.5?
We also have IdentityServer for SSO and internal applications hosted on the same machine on IIS.
And I faced with the same problem too.
Here is a solution:
1) You need to solve Owin/Katana middleware problem to avoid nonce overfloating. Here you can find the code for that fix
2) You have to stop sharing cookies.
So if your base address for applications is "mysite.com".
And you have a lot of different applications like this:
Good App: mysite.com/good_app/
Best App: mysite.com/best_app/
Super App: mysite.com/super_app/
Use CookiePath for each application on an application's side and it will limit cookies (and look here too).
Use the code like this (for "Good App"):
var cookieOptions = new CookieAuthenticationOptions
{
AuthenticationType = "Cookies",
CookieName = "GoodAppCookies",
// Cookie Path same as application name on IIS
CookiePath = "/good_app
};
Hope it'll help.
Few things that you can try. Make the following changes at the server level.
Highlight the server name in IIS, select "configuration editor", select "system.web" and "httpRuntime" and change "maxRequestLength" to "1048576".
You can also edit the "applicationHost.config" file in the following way- C:\Windows\System32\inetsrv\Config
<configuration>
<system.web>
<httpRuntime maxRequestLength="1048576" />
</system.web>
</configuration>
Edit "Request Filtering" settings at server level on IIS and set "maxAllowedContentLength" to "1073741824"
You can also edit the root web.config file in the following manner - C:\Windows\Microsoft.NET\Framework64*\v4.0.30319*\Config
*Folder is based on your application. if its a 32 bit application, navigate to "Framework" folder. If its a .net 2.0 application, navigate to v2.0.50727.
<system.webServer>
<security>
<requestFiltering>
<requestLimits maxAllowedContentLength="1073741824" />
</requestFiltering>
</security>
</system.webServer>
First of all - I want to say that I have not tried this myself, so I can't assure that it is a solution, but I'm trying to help.
The problem with the cookies originates from the Microsoft OWIN/Katana and the way they are encrypting them. They become enormous, but this has nothing to do with Identity Server. However here and here there are good discussion around this.
The main thing to try first is in the Startup.cs of the IdentityServer project, in the IdentityServerOptions.AuthenticationOptions there is a property SignInMessageThreshold which defaults to 5. Try setting it to something lower, this will keep your header smaller (which may cause round trips to identity server when an app doesn't have its message in the cookies, but this will not force the user to re-login).
Another thing, that we achieved in one of out projects, is to create a DataBase backed cookie session handler. In your clients, where you use
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies",
CookieName = cookieName,
});
There is also a property SessionStore. You can have a custom implementation of the Microsoft.Owin.Security.Cookies.IAuthenticationSessionStore. In our case this reduced the cookie size to less than (or around) 300.

ELMAH - Get SMTP credentials from Azure Application Settings

I've got an Azure Web App using ELMAH to log unhandled exceptions.
When I first deployed it, the web.config had the full SMTP setup defined in it, and ELMAH emailed exceptions:
<system.net>
<mailSettings>
<smtp from="me#mydomain.com">
<network host="smtp.mailprovider.com"
port="123"
userName="myUserName"
password="p#ssw0rd" />
</stmp>
</mailSettings>
</system.net>
The username and password have since been removed from the web.config, and they're now stored as application settings, configured through the Azure Portal.
Most of the emails I send still work fine, as the email code can access these application settings and use them when instantiating the SmtpClient, e.g.:
var userName = WebConfigurationManager.AppSettings["smtp.userName"];
var password = WebConfigurationManager.AppSettings["smtp.password"];
var credentials = new NetworkCredential(userName, password);
using (var smtpClient = new SmtpClient { Credentials = credentials })
{
await smtpClient.SendMailAsync(mailMessage);
}
What's the best way to get ELMAH to use the credentials stored in the application settings?
Options I can see:
There is a page on the wiki explaining how to use ELMAH's ErrorTweetModule to do an HTTP form post with the error details to any URL. The controller receiving the post could then use the stored credentials to email the details on.
The WebBase has a link to an article suggesting you can send emails directly to the recipient's SMTP server without authentication, but it says this may not work if you have DomainKeys set up, which I do.
This answer links to an article about intercepting the Mailing event, to customise the message.
I ended up creating a custom version of Elmah's ErrorMailModule, derived from the standard one, but overriding the SendMail method, based on some advice from Atif Aziz in a discussion on Google Groups.
The only changes required were to create the new module, and switch the Web.Config to use the custom module instead of the standard one.
Module
using System;
using System.Net.Mail;
namespace Test
{
public class ErrorMailModule : Elmah.ErrorMailModule
{
protected override void SendMail(MailMessage mail)
{
if (mail == null) throw new ArgumentNullException(nameof(mail));
// do what you want with the mail
// (in my case this fires up the email service, which
// gets the credentials from the Azure settings)
}
}
}
Web Config Changes
All that's required is to change the two occurrences of Elmah.ErrorLogModule, Elmah to your own module, in this case Test.ErrorMailModule.
So, instead of this...
<system.web>
<httpModules>
<add name="ErrorMail" type="Elmah.ErrorMailModule, Elmah" />
</httpModules>
</system.web>
<system.webServer>
<modules>
<add name="ErrorMail" type="Elmah.ErrorMailModule, Elmah" preCondition="managedHandler" />
</modules>
</system.webServer>
...you should now have this:
<system.web>
<httpModules>
<add name="ErrorMail" type="Test.ErrorMailModule" />
</httpModules>
</system.web>
<system.webServer>
<modules>
<add name="ErrorMail" type="Test.ErrorMailModule" preCondition="managedHandler" />
</modules>
</system.webServer>
You will still need the errorMail section, as Elmah is still responsible for creating the email. Mine looks like this:
<elmah>
<errorMail from="user#domain.com" to="user#domain.com" subject="Custom Email Module"/>
</elmah>
Creating a HTTP request could work, but that should be the solution if everything else doesn't work IMO. Intercepting the Mailing event doesn't work, since you do not have access to the SmtpClient with the credentials in that event.
I've looked at different ways to update the SMTP settings from code. At first I though that I could just get a reference to the smtp section and update the properties, since they all have setter. But the code throw a configuration exception on runtime.
From what I can find, the only way to update the username and password in smtp section, is to read the web.config, update it and write the new version. Here's an example of writing updates to web.config:
var configuration = WebConfigurationManager.OpenWebConfiguration("~");
var section = configuration.GetSection("system.net/mailSettings/smtp") as SmtpSection;
section.Network.UserName = ConfigurationManager.AppSettings["myusername"];
section.Network.Password = ConfigurationManager.AppSettings["mypassword"];
configuration.Save();
The code actually updates the web.config. The code can be run at startup, but that would modify your web.config file locally as well. Another approach would be to run the code as part of a post deployment task with Azure.

SharePoint custom form

I am looking to create a page with a single form on it that does the following:
Contact a webservice with input from the form.
Perform an action (programmed using C#) depending on the result of the webservice call.
Since I am not interacting with any lists or similar on the SharePoint site, I was thinking a WebPart would be the simplest way to add the form and catch the submit-event, but I am not sure if this is the best practice or an easier/better way exists.
I also need to restrict access to the form to a specific usergroup.
Thanks in advance!
A new SharePoint Web Part is probably the most common way to provide this solution in SharePoint and fits your requirements well. Though your solution doesn't call for it, you do have access to the lists from custom web part code.
If you are using SharePoint 2007, Visual Studio Extensions provide the Microsoft supported way to create one easily. It's much easier with Visual Studio 2010 and SharePoint 2010.
Some other options would be an InfoPath Form with custom code or a custom application page with code behind. The benefit of the web part is that it works with all versions of SharePoint and can be added to any web part page on the site and customized by users. Also, the application page may not pick up the master page if you are on SharePoint 2007.
Use WebDAV to upload an ASPX page to a site in SharePoint. Then upload your assembly to each SharePoint server, the bin folder of your application is preferred, or add it to the GAC.
Your ASPX page might look like this:
<%# Page Language="C#" masterpagefile="~masterurl/custom.master" inherits="MyAssembly, MyClass, Version=1.0.0.0, Culture=neutral, PublicKeyToken=c48b11b32c9eb4a7" %>
<asp:Content runat="server" ContentPlaceholderID="PlaceHolderPageTitle">My Title</asp:Content>
<asp:Content runat="server" ContentPlaceholderID="PlaceHolderPageTitleInTitleArea">My Page</asp:Content>
<asp:Content runat="server" ContentPlaceholderID="PlaceHolderMain">
<asp:Button runat="server" ID="ButtonClickMe" Text="Click Me!" />
</asp:Content>
Then your assembly might look something like this:
public class MyClass : Microsoft.SharePoint.WebPartPages.WebPartPage
{
protected global::System.Web.UI.WebControls.Button ButtonClickMe;
protected override void OnLoad(EventArgs e)
{
base.OnLoad( e );
ButtonClickMe.Click += new EventHandler( ButtonClickMe_Click );
}
void ButtonClickMe_Click(object sender, EventArgs e)
{
if (!Page.IsValid)
return;
// Do stuff here
}
}
You won't be able to edit the permissions of the ASPX page directly, but you can manipulate the permissions of the site it is in (thus, restrict the site to only the usergroup which you want to access the form).

I've got a sharepoint solution, what next

I'm new to sharepoint. I have a C# solution, that has masterpages and user-controls to be used in a sharepoint site. I have setup my sharepoint dev VM and i can browse the default sharepoint stuff.
How do I add the master pages to Sharepoint? Where do I go from here?
My suggestion would be to deploy the master pages as a feature rather than a manual process. Solutions (WSPs) and Features are the supported way to deploy content/features into sharepoint. A really great tool for sharepoint development is called WSPBuilder
A master page is deployed into sharepoint as a "module" that you will place into your elements.xml file in the feature.
Think of a solution as a .cab file with a different extension. Within that is a file called feature.xml which defines the title of your package when its deployed. Features can be activated and deactivated to deploy and undeploy your content into parts of your farm.
Here is an example of a css file deployed as a Module... Master pages would be similar however, they would deploy into the master page gallery rather than the style library. This module deploys a custom css file into the "Style Library" of a site collection. After this is deployed I used a "Feature Receiver" (event handler) to grab a reference to the SPSite object and modify its alternate stylesheet so that my override took place.
Feature.xml
<?xml version="1.0" encoding="utf-8" ?>
<Feature xmlns="http://schemas.microsoft.com/sharepoint/"
Id="63BB13A0-1F9C-4c3b-BE60-10E59CEE0113"
Title="Custom CSS Feature"
Description="Deploying a custom CSS using a feature"
Version="1.0.0.0"
Hidden="FALSE"
Scope="Site"
ReceiverAssembly="CustomCSSFeature, Version=1.0.0.0, Culture=neutral, PublicKeyToken=24f1377a8414d2ed"
ReceiverClass="CustomCSSFeature.FeatureReceivers.CustomCSSFeatureReceiver"
>
<ElementManifests>
<ElementManifest Location="elements.xml"/>
</ElementManifests>
</Feature>
elements.xml - you'd modify this to reflect where master pages are supposed to be deployed I would think that this is the Url property. The Path="Styles" refers to the relative path within the feature itself where the style sheet resides (e.g. in your visual studio i have a sub folder called styles beneath the folder called CustomCSSFeature and that is where the style sheet exists)
<?xml version="1.0" encoding="utf-8" ?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<Module Name="OSGStyles" Url="Style Library" Path="Styles" RootWebOnly="TRUE">
<File Url="custom-css.css" Type="GhostableInLibrary" />
</Module>
</Elements>
Then, in my feature receiver class I have activated/deactivated handlers which "apply" the stylesheet to the publishing web. In your case you can likely change the default master page for the website in a feature receiver as well.
public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
SPSite site = properties.Feature.Parent as SPSite;
using (SPWeb web = site.OpenWeb())
{
PublishingWeb publishingWeb = PublishingWeb.GetPublishingWeb(web);
publishingWeb.AlternateCssUrl.SetValue(web.ServerRelativeUrl +
"/Style Library/custom-css.css", true);
publishingWeb.Update();
web.Update();
}
}
public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
{
SPSite site = properties.Feature.Parent as SPSite;
using (SPWeb web = site.OpenWeb())
{
PublishingWeb publishingWeb = PublishingWeb.GetPublishingWeb(web);
publishingWeb.AlternateCssUrl.SetValue("", true);
publishingWeb.Update();
web.Update();
}
}
Copy them to SharePoint root (For SP 2007 default location is C:\Program Files\Common Files\microsoft shared\Web Server Extensions\12\, for SP 2010 instead of "12" you have "SharePointRoot")
From there, copy your files to \TEMPLATE\LAYOUTS folder and then you can reference masterpage from your aspx pages like "/_layouts/mymasterpage.master".
UserControls goes into \TEMPLATE\CONTROLTEMPLATES
Get to know the directory structure in the 12 Hive
Exploring the 12 Hive : TEMPLATE Directory
Another way is to put your masterpage in master pages list. Use this link to access master page list and upload your masterpage: http:///_catalogs/masterpage
You can add master pages using the SharePoint Designer 2007.
Generally i recommend you to take a look at the answers of this question: Learning Sharepoint

Resources