I am creating an MVC application with forms auth. I am authenticating against active directory and so have created a custom RoleProvider. My application is only concerned with a small set of roles which up until now I have been defining in the appSettings section of my web.config:
<appSettings>
<add key="DirectorRole" value="Domain\Directors" />
<add key="ManagementRole" value="Domain\Managers" />
...
</appSettings>
However I have run into a couple of problems with this approach:
I cannot reference these setting in my contoller data annotations: [Authorize(Roles = ConfigurationManager.AppSettings["DirectorRole"])] as it wont compile so I have to specify the name of the group again: [Authorize(Roles = "Domain\\Directors")].
In my web.config, I would like to specify the groupsToUse for my role provider and just reference a pre-existing list, rather than maintain two seperate lists of the same set of roles.
It seems that there must be a better/reusable way to define the roles in the web.config, can someone point me in the right direction please?
I would prefer using a custom authorize attribute. Like this one.
public class MyAuthorizeAttribute : AuthorizeAttribute {
public MyAuthorizeAttribute(params string[] roleKeys) {
List<string> roles = new List<string>(roleKeys.Length);
//foreach(var roleKey in roleKeys) {
//roles.Add(ConfigurationManager.AppSettings["DirectorRole"]);
//}
var allRoles = (NameValueCollection)ConfigurationManager.GetSection("roles");
foreach(var roleKey in roleKeys) {
roles.Add(allRoles[roleKey]);
}
this.Roles = string.Join(",", roles);
}
}
In your controller, use:
[MyAuthorize("DirectorRole")]
In your web.config
<configSections>
<section
name="roles"
type="System.Configuration.NameValueFileSectionHandler,System, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
</configSections>
<roles>
<add key="DirectorRole" value="Domain\Directors" />
<add key="ManagementRole" value="Domain\Managers" />
</roles>
I hope this will solve your first problem just fine. And twiking a little will solve the second one too.
Please have a look at this excellent example, in which author talks about the problem you are facing.
http://www.ryanmwright.com/2010/04/25/dynamic-controlleraction-authorization-in-asp-net-mvc/
Related
We have a multi-tenanted MVC app, meaning that exactly the same app is published to multiple IIS virtual directories / applications, and then the app its self works out who it is, and skins its self (css) accordingly.
This is all very well, but anything logged by ELMAH in our elmah database gets logged under the same applicationName, as this is pulled out of Web.Config elmah section below where everything would be logged as "MyappName" :
<configuration>
[...]
<elmah>
<security allowRemoteAccess="false" />
<errorLog
type="Elmah.SqlErrorLog, Elmah"
connectionStringName="elmah"
applicationName="MyappName" />
</elmah>
</configuration>
The question is therefore how to override the applicationName setting from web.config with something specific so we can distinguish errors for a given tenant web site.
As this is configurable within the web.config, ELMAH are already providing you with a way to specify the application name when the application is deployed to different locations - it's just a case of making use of it.
This would generally be something that you would manipulate as part of your deployment steps. If you are doing it manually then it's going to be a pain, but it could be easily manipulated by using a web.config transform.
<?xml version="1.0"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<elmah>
<errorLog applicationName="MyappName" xdt:Transform="SetAttributes" xdt:Locator="Match(name)" />
</elmah>
</configuration>
I wonder if the following might work, if you put the following into your Global.asax:
var service = ServiceCenter.Current;
ServiceCenter.Current = context =>
{
var connectionString = "YOUR CONNECTION STRING";
var container = new ServiceContainer(service(context));
var log = new SqlErrorLog(connectionString) { ApplicationName = "APP NAME HERE" };
container.AddService(typeof(ErrorLog), log);
return container;
};
I want to restrict one domain for my application in web.config. How can I do it? I just found solution for IIS settings, like
<add ipAddress="some_ip_address" />
but I want to restrict a domain, not IP address
There is no direct way to not allow specific domain.You can restrict certain IPs only.
You can always use nsloopkup to map domain to its IP addresses.
<security>
<ipSecurity allowUnlisted="true">
<clear/> <!-- removes all upstream restrictions -->
<add ipAddress="83.116.19.53"/>
</ipSecurity>
</security>
There is no direct setting in the web.config to do this. However you can create a custom collection in the web.config to list your domains and the write a custom requesthandler or a custom actionfilter that would block the specific domain. However this can very easily be spoofed or bypassed. Here is an example:
Your configsection class:
public class DisallowedDomainsSection : ConfigurationSection
{
// Create a "remoteOnly" attribute.
[ConfigurationProperty("remoteOnly", DefaultValue = "false", IsRequired = false)]
public Boolean RemoteOnly
{
get
{
return (Boolean)this["remoteOnly"];
}
set
{
this["remoteOnly"] = value;
}
}
// Create a "domain" element.
[ConfigurationProperty("domainName")]
public DomainElement Domain
{
get
{
return (DomainElement)this["domainName"]; }
set
{ this["domainName"] = value; }
}
}
Then you declare your config section in the configSections node:
<configSections>
<sectionGroup name="disAllowDomainsGroup">
<section
name="disallowDomains"
type="your.class.definition"
allowLocation="true"
allowDefinition="Everywhere"
/>
</sectionGroup>
</configSections>
Then you declare the action section:
<configuration>
<!-- Configuration section settings area. -->
<disallowDomainsGroup>
<disallowDomain remoteOnly="true">
<domainName="www.google.com" />
</dissallowDomain>
</disallowDomainsGroup>
</configuration>
Finally you create a ActionFilterAttribute to get the list of disallowed domains and redirect/restrict the user if they are originating from the domain.
You can access the newly created webconfig section like this:
DomainsDisallowedSection config =
(DomainsDisallowedSection)System.Configuration.ConfigurationManager.GetSection(
"disallowDomainsGroup/disallowDomains");
and then iterate through your disallowed domains and use
HttpContext.Current.Request.Url.Host
to match the disallowed domain with the request domain. But as previously stated this can be bypassed quite easily by someone who knows how.
In IIS Manager:
Select the desired site.
In the Home pane, open IP Address and Domain Restrictions.
In the Actions pane, launch Edit Feature Settings...
Check the box for Enable domain name restrictions
Click [OK] to exit.
You can now add domain names the same way you add IP addresses.
Note: This cannot be done through web.config. The settings are stored in ApplicationHost.config. You will have to get access to IIS Manager to enable IP and Domain Security though. There is no way around this.
<ipSecurity enableReverseDns="true">
<add ipAddress="192.168.0.1" allowed="false" />
<add domainName="google.com" allowed="false" />
</ipSecurity>
I have developed my own classes for Custom Membership and Role providers.
Everything works locally. Nonetheless, after deploying the solution to IIS for testing, my login action seems to work (at least, the action validates the username+password and the user appears to be authenticated) but whenever I try to access actions that are decorated with annotations like
[Authorize(Roles="Employee, Admin")]
I keep getting redirected to the login page, as if the user didn't have the necessary role (he does, though).
So locally, the application succeeds in validating users and checking the authenticated user's roles before executing actions (thus, I assume that my methods on both classes are correct) but on IIS it looks like the role provider isn't working properly. Anyone happens to know where might I be wrong or how can I get a better view on my problem?
In my Web.Config:
<system.web>
(...)
<authentication mode="Forms">
<forms loginUrl="~/Account/Login" timeout="2880" />
</authentication>
<membership defaultProvider="CustomMembershipProvider">
<providers>
<clear />
<add name="CustomMembershipProvider" type="MyApplication.Infrastructure.CustomMembershipProvider" connectionStringName="DBEntities" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="false" maxInvalidPasswordAttempts="5" minRequiredPasswordLength="6" minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10" applicationName="/" />
</providers>
</membership>
<roleManager enabled="true" defaultProvider="CustomRoleProvider">
<providers>
<clear />
<add name="CustomRoleProvider" type="MyApplication.Infrastructure.CustomRoleProvider" connectionStringName="DBEntities" applicationName="/" />
</providers>
</roleManager>
(...)
</system.web>
Thanks in advance.
EDIT: Aditional Info.
I just modified one of my actions' anotation to simply [Authorize] and it works. So, I believe that the authentication works and the problem must be related to the Role provider.
I'm using Entity Framework for my Data Model, the con. string is as follows:
I managed to register a user and log in using the that newly created account, which would mean that the DB connection and the Custom Membership Provider(?) are working properly.
A "#foreach (String str in Roles.GetRolesForUser(User.Identity.Name)){#str} prints the roles locally and doesn't print anything when deployed.
Ok, I fixed it. Here's the explanation in case someone needs it in the future:
After narrowing out the causes (as seen in my edits), I figured out that the problem must be related to my CustomRoleProvider.
That class has methods like this one:
public override string[] GetRolesForUser(string Username)
{
List<string> roles = new List<string>();
using (DBEntities _db = new DBEntities())
{
try
{
var dbRoles = from r in _db.UserRole
where r.Users.Username == Username
select r;
foreach (var role in dbRoles)
{
roles.Add(role.Role.Name);
}
}
catch
{
}
}
return roles.ToArray();
}
So I was catching an exception and not doing anything with it. I removed the try-catch block, and got this message:
There is already an open DataReader associated with this Command which must be closed first.
A bit of stackoverflowing and I found this: There is already an open DataReader associated with this Command which must be closed first
Turns out my local connection string had MultipleActiveResultSets=true but the connection string on my publish settings didn't. I modified my publish settings and voilĂ , seems to be working now.
I don't really know the advantages/disadvantages of having that setting, but it is working and I really need to move on. Thank you all for your help anyway.
I have had the similar issue. After adding machineKey to web.config everything works all right.
<system.web>
<machineKey validationKey="2E417D4AC04F20FA6CE1CF1EFE23FBF1695BF6981B605B1B8628D2182C43D0B10E48C4A83FDCE0D1D6300095D9EE1B8746A37E2C3256554405983DCAA7622875" decryptionKey="FA6D35C22BF7E5E9E4438052B924CCC017521137C5EB017D07C7038B80C5F726" validation="SHA1" decryption="AES" />
</system.web>
I've set up my application to use a custom role provider by adding some lines to the Web.config file, like so:
<roleManager enabled="true" defaultProvider="AspNetSqlRoleProvider">
<providers>
<!-- <clear/>-->
<add name="CustomRoleProvider"
connectionStringName="Custom"
applicationName="Custom"
type="Authorization.CustomRoleProvider" />
</providers>
</roleManager>
I've created an empty Authorization.CustomRoleProvider class and added references to it.
Now my code has one simple test case in it, like so:
[Authorize (Roles= "Admin")]
public ActionResult Index(Model model)
As far as I can tell, none of the code I've written so far is being called (if it would, it would raise an exception on account of methods not being implemented). Am I messing something up in my configuration?
You should change your default provider name to match your provider name of "CustomRoleProvider":
<roleManager enabled="true" defaultProvider="CustomRoleProvider">
I am having trouble with Crystal Reports when using charts and images which use CrystalImageHandler.aspx. The image cannot display and I suspect this is due to a problem with MVC routing.
The path image path is similar to this:
src="/CrystalImageHandler.aspx?dynamicimage=cr_tmp_image_a8301f51-26de-4869-be9f-c3c9ad9cc85e.png"
With the URL similar to this:
localhost:01234/ViewCrystalReports.aspx?id=50
The image cannot be found prumably because it's looking in a non-existant directory. How can I change the path CrystalImageHandler.aspx is located at? I think if I were to reference from the root the problem would be solved but anything I change in Web.Config fails to work.
I should mention this is on a conventional aspx page, not a view etc
I solve this problem editing Web.Config file
Insert the following line:
<system.web>
...
<httpHandlers>
<add path="CrystalImageHandler.aspx" verb="GET" type="CrystalDecisions.Web.CrystalImageHandler, CrystalDecisions.Web, Version=13.0.2000.0, Culture=neutral, PublicKeyToken=692fbea5521e1304"></add>
</httpHandlers>
...
*take care with write your number version (Version=xx.x.xxxx.x)
Figured it out. The routing was interfering with the CrystalImageHandler.aspx link that was being generated. Global.aspx has the following line to tell the routing engine to ignore resource files:
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
but this isn't a conventional resource file, it's an aspx file for some reason (anyone know why?)
adding this fixed it:
routes.IgnoreRoute("{resource}.aspx/{*pathInfo}");
public class CrystalImageHandlerController : Controller
{
//
// GET: /Reports/CrystalImageHandler.aspx
public ActionResult Index()
{
return Content("");
}
protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
var handler = new CrystalDecisions.Web.CrystalImageHandler();
var app = (HttpApplication)filterContext.RequestContext.HttpContext.GetService(typeof(HttpApplication));
if (app == null) return;
handler.ProcessRequest(app.Context);
}
}
This controller will invoke the handler. Just add a route to this as CrystalImageHandler.aspx, it can also be used with any sub path you'd like (in this case /reports). Something I could NEVER get the handler to do via configuration.
To view in local machine,you will add the following code in web config
<httpHandlers>
<add verb="GET" path="CrystalImageHandler.aspx" type="CrystalDecisions.Web.CrystalImageHandler, CrystalDecisions.Web,Version=10.2.3600.0, Culture=neutral, PublicKeyToken=692fbea5521e1304" />
</httpHandlers>
...............................
<appSettings>
<add key="CrystalImageCleaner-AutoStart" value="true" />
<add key="CrystalImageCleaner-Sleep" value="60000" />
<add key="CrystalImageCleaner-Age" value="120000" />
</appSettings>
The following code is for displaying in server
<system.webServer>
<handlers>
<add name="CrystalImageHandler.aspx_GET" verb="GET" path="CrystalImageHandler.aspx" type="CrystalDecisions.Web.CrystalImageHandler, CrystalDecisions.Web, Version=10.2.3600.0, Culture=neutral, PublicKeyToken=692fbea5521e1304" preCondition="integratedMode"/>
</handlers>
</system.webServer>
:) I will solve that problem in adding in web config
It's because the routing was interfering with the CrystalImageHandler.aspx. So either in Global.asax or routeConfig file we can ignore route for .aspx extension files. You can ignore .aspx extension route by adding following line.
routes.IgnoreRoute("{allaspx}", new {allaspx=#"..aspx(/.*)?"});