bonobo git server with active directory - bonobo

I have Bonobo Git server (5.1.1.0) set up with Active Directory membership service (Running on Win 2012 R2). The group specified in the ActiveDirectoryMembergroupName has only two accounts added. The Administrator group specified in ActiveDirectroyRoleMapping has only one account (which is duplicated in the users group). When I go to the Users tab or to set permissions on a repository there are about 120 accounts listed. Most are accounts that have admin access to the server however a few of them I can't identify (other than being from the domain)
Here is the modified section of my web.config file:
<add key="AuthenticationProvider" value="Cookies" />
<!--<add key="AuthenticationProvider" value="Windows" />-->
<!--<add key="AuthenticationProvider" value="Federation" />-->
<!--<add key="MembershipService" value="Internal" /> -->
<add key="MembershipService" value="ActiveDirectory" />
<add key="ActiveDirectoryDefaultDomain" value="MY_DOMAIN.ORG" />
<add key="ActiveDirectoryBackendPath" value="~\App_Data\ADBackend" />
<add key="ActiveDirectoryMemberGroupName" value="MY_GIT_USERS" />
<!--<add key="ActiveDirectoryTeamMapping" value="Developers=GitTeam" /> -->
<add key="ActiveDirectoryRoleMapping" value="Administrator=MY_GIT_ADMINS" />
Does anyone know what I might be doing wrong here?
Thanks in advance.

I had to restart the IIS service for something unrelated to this, when it restarted the extraneous user entries were gone.

I answer I even has my problem.
Concerns of my accounts came from the not piece of information of UPN (UserPrincipalName) field certainly used by Bonobo.
Thus I made a power shell script getting back the field SamAccountName to inform UPN:
# Import du module Active Directory
import-module ActiveDirectory
# Récupération de tous les utilisateurs de l’AD dont le champ d'ouverture de session (UPN) est non renseigné
$users = Get-ADUser -Filter {UserPrincipalName -notlike "*"} -SearchBase "OU=myOU,DC=company,DC=my" -properties SamAccountName
# Boucle qui pour chaque utilisateur modifie son UPN
foreach ($user in $users) {
# Modification des UPNs
#Mise à jour de l’UPN sur $($user) à la valeur $($UPN) »
$user | Set-ADUser -UserPrincipalName $user.SamAccountName
Write-Output $user.SamAccountName
}

Related

How does WIF (WSFederationAuthentication) know which user I am?

I've been put in charge of figuring out a way to allow users to authenticate into our forms based site. Our corporate IT has set up a development adfs server, and the relying party trust has been set up to one of our dev environments.
I've been reading and looking for tutorials for about two weeks, so I'm by no means an expert, and I can't seem to understand how the STS (our ADFS server) is supposed to figure out which user is requesting authentication.
I've been following Wiktor Zychla's blog because it actually includes code examples and messes less with the web.config as opposed to most other tutorials. (http://www.wiktorzychla.com/2014/11/simplest-saml11-federated-authentication.html)
Here's the pipeline so far as I can see it.
User comes to the log in page.
Page_Load automatically redirects user to ADFS
User gets authenticated and redirected back to the log in page
In the Page_Load I then consume some token and use that to authorize
the user.
Redirect the user to a page that requires authentication.
Code and Web.config changes I've made to the site:
protected void Page_Load(object sender, System.EventArgs e)
{
var sam = FederatedAuthentication.SessionAuthenticationModule;
var fam = new WSFederationAuthenticationModule();
fam.FederationConfiguration = FederatedAuthentication.FederationConfiguration;
var request = new HttpContextWrapper(this.Context).Request;
if (UseADFS)
{
// is this the response from the STS
if (!fam.IsSignInResponse(request))
{
// no
// the STS
fam.Issuer = WsFederationIssuerName;
// the return address
fam.Realm = WsRealm;
fam.Reply = WsReply;
var req = fam.CreateSignInRequest(string.Empty, null, false);
// go to STS
Response.Redirect(req.WriteQueryString());
}
// is this the response from the STS
else
{
// yes, get the SAML token
var securityToken = fam.GetSecurityToken(request);
var config = new SecurityTokenHandlerConfiguration
{
CertificateValidator = X509CertificateValidator.None,
IssuerNameRegistry = new CustomIssuerNameRegistry()
};
config.AudienceRestriction.AudienceMode = AudienceUriMode.Never;
var tokenHandler = new SamlSecurityTokenHandler
{
CertificateValidator = X509CertificateValidator.None,
Configuration = config
};
// validate the token and get the ClaimsIdentity out of it
var identity = tokenHandler.ValidateToken(securityToken);
var principal = new ClaimsPrincipal(identity);
var token = sam.CreateSessionSecurityToken(principal, string.Empty,
DateTime.Now.ToUniversalTime(), DateTime.Now.AddMinutes(20).ToUniversalTime(), false);
sam.WriteSessionTokenToCookie(token);
if (identity[0].IsAuthenticated)
{
//string email = principal.Claims.Where(x => x.Type == ClaimTypes.Email).Select(x => x.Value).SingleOrDefault();
string name = principal.Claims.Where(x => x.Type == ClaimTypes.Name).Select(x => x.Value).SingleOrDefault();
CustomClaimsObject claim = new CustomClaimsObject(name, principal);
doSigninWork(true, claim);
}
}
}
else if (!this.IsPostBack && !fam.IsSignInResponse(request))
{
Session.Abandon();
}
}
<configSections>
<section name="system.identityModel" type="System.IdentityModel.Configuration.SystemIdentityModelSection, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
<section name="system.identityModel.services" type="System.IdentityModel.Services.Configuration.SystemIdentityModelServicesSection, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
</configSections>
<httpModules>
<add name="SessionAuthenticationModule" type="System.IdentityModel.Services.SessionAuthenticationModule, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" preCondition="managedHandler"/>
</httpModules>
<modules>
<add name="SessionAuthenticationModule" type="System.IdentityModel.Services.SessionAuthenticationModule, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" preCondition="managedHandler"/>
</modules>
<system.identityModel>
</system.identityModel>
<system.identityModel.services>
<federationConfiguration>
<cookieHandler requireSsl="false" />
</federationConfiguration>
</system.identityModel.services>
<authentication mode="Forms">
<forms loginUrl="Public/invalidlogin.aspx?err=sessiontimeout" protection="All" requireSSL="false" slidingExpiration="true"/>
</authentication>
How is the STS supposed to figure out who is requesting authentication? I'm not posting any relevant user info to it. Am I supposed to add something to my request? Or is the context somehow supposed to have relevant data? Or does the ADFS box need to have a log in where the user enters their credentials?
Right now when I navigate to the user to (by way of the button click) https://xxx-xxx.xxxxx.xxxxxx.com/adfs/ls/ I get this error.
Encountered error during federation passive request.
Additional Data
Protocol Name:
wsfed
Relying Party:
Exception details:
Microsoft.IdentityServer.Web.CookieManagers.InvalidContextException: MSIS7001: The passive protocol context was not found or not valid. If the context was stored in cookies, the cookies that were presented by the client were not valid. Ensure that the client browser is configured to accept cookies from this website and retry this request.
at Microsoft.IdentityServer.Web.Protocols.GenericProtocolRequest.ParseEncodedRequestParts(String[] encodedRequestParts)
at Microsoft.IdentityServer.Web.Protocols.GenericProtocolRequest..ctor(String encodedGenericRequest)
at Microsoft.IdentityServer.Web.Protocols.WSFederation.WSFederationProtocolHandler.GetOriginalRequestFromResponse(ProtocolContext context)
at Microsoft.IdentityServer.Web.PassiveProtocolListener.ProcessProtocolRequest(ProtocolContext protocolContext, PassiveProtocolHandler protocolHandler)
at Microsoft.IdentityServer.Web.PassiveProtocolListener.OnGetContext(WrappedHttpListenerContext context)
Can someone kick me in the right direction? I'm sure I'm missing something obvious to anyone with experience.
Update:
Login with adfs working. But now I'm struggling a bit with the logout.
My logout code is below. Essentially whats happening is the authentication on my claims identity is still true even after I've attempted to "logout" with the code below. The only claim I'm sending from ADFS to my relying party is en E-mail claim. Not sure if that makes a difference.
WSFederationAuthenticationModule authModule = FederatedAuthentication.WSFederationAuthenticationModule;
string signoutUrl = (WSFederationAuthenticationModule.GetFederationPassiveSignOutUrl(WsFederationIssuerName, eURL.BuildURL(), null));
WSFederationAuthenticationModule.FederatedSignOut(new Uri(signoutUrl), null);
Update 2:
So I've realized the sign out is working correctly. However it seems ADFS is redirecting to my sign in page before it redirects to my log out page. This is causing the Sign in code to be run again which fires another claim to ADFS. Is there a way to stop this? Or a way to know if I'm coming from the adfs logout?
Update 3:
Solved the issue by wrapping an if statement around the if(UseADFS) clause to check to see where I'm coming from.
if(request.UrlReferrer.Authority.Contains(authority))
I just check to see if the place I'm coming from is my server (your adfs domain) vs (your website domain). This together with a parameter placed in my redirect url was enough to decipher whether the user was logging out or logging in.

Request returns 404 Not Found for OPTIONS, but works fine when using Postman (ASP.NET)?

Browser console output:
XMLHttpRequest cannot load https://api.[...].com/[...]. Response to
preflight request doesn't pass access control check: No
'Access-Control-Allow-Origin' header is present on the requested
resource. Origin 'http://localhost' is therefore not allowed access.
The response had HTTP status code 404.
I have an AngularJS application that is making calls to an API built in ASP.NET. Recently the backend team added versioning to the application, and for some reason, one of the API endpoints will no longer return 200 OK for the OPTIONS request (even though all the other API endpoints on that same server still return 200 OK). Always return 404 Not Found.
ASP.NET server seems to be using a WebApi.Cors package; no specific [HttpOptions] methods are being declared (all OPTIONS request are handled through the package); and the web.config feeds the CorsConfig with * for all of origin, headers, methods.
I've tried many of the solutions from Google results but nothing has worked.
Anyone face a similar issue and can give general guidance on what could be causing the issue, or how to potentially test for the problem, or attempt a solution?
[Edit:] Found solution.
Issue caused because "Version 1" of the API endpoint was dropped on that specific route. The first valid version on that endpoint was now "Version 2". So I added a blank Controller method to catch requests for "Version 1" (which only returns a blank string, nothing more), and this was sufficient to allow the OPTION request to resolve.
I think part of the issue here is the routing has changed:
Recently the backend team added versioning to the application
Things to check:
WebApi Configuration
Can you make sure that your configuration takes place as the first item in your Global.asax file:
void Application_Start(object sender, EventArgs e)
{
GlobalConfiguration.Configure(WebApiConfig.Register);
//...
}
Web API Routing
Has the versioniong been correctly configured inside the WebApiConfig?
public static void Register(HttpConfiguration config)
{
config.EnableCors(new EnableCorsAttribute("*", "*", "*");
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/v2/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
}
In IIS 7.5 the only way I got CORS working was via the web.config and not through the Nuget package:
My web.config was as follows:
<remove name="OPTIONSVerbHandler" />
<remove name="TRACEVerbHandler" />
<remove name="ExtensionLessUrlHandler-ISAPI-4.0_32bit" />
<remove name="ExtensionLessUrlHandler-ISAPI-4.0_64bit" />
<remove name="ExtensionLessUrlHandler-Integrated-4.0" />
<add name="ExtensionLessUrlHandler-ISAPI-4.0_32bit" path="*." verb="*" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" />
<add name="ExtensionLessUrlHandler-ISAPI-4.0_64bit" path="*." verb="*" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" />
<add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
</handlers>
</system.webServer>
Found the solution. Posting it here for anyone else that has the problem in the future.
The API endpoint started with "version 2". There had been an earlier "version 1" that never made it to production, but it was dropped/discontinued because the signature would be completely changing. This was done because development teams had been using "version 1" up to that point, but since it wasn't going into production, logic dictated no longer having that version/endpoint.
For some reason, not having a "version 1" caused the OPTIONS to fail and return a 404 Not Found. My guess is because the required version header was not actually included with the OPTIONS pre-flight request, so it never resolved to a GET destination in the Controller.
Thus, you must have a version 1 reference, even if it's a placeholder that just returns a blank string.
Before:
[VersionedRoute("products", 2, Name = "GetProducts")]
[HttpGet]
public IHttpActionResult GetProducts([FromUri] GetProductsRequest request)
{
After:
[VersionedRoute("products", 1, Name = "GetProducts")]
[HttpGet]
public IHttpActionResult GetProducts()
{
    return NotFound();
}
[VersionedRoute("products", 2, Name = "GetProducts_V2")]
[HttpGet]
public IHttpActionResult GetProducts_V2([FromUri] GetProductsRequest request)
{

Lock users out of Active Directory with ASP.NET application

We have an intranet ASP.NET 4.0 application and use forms authentication where employees authenticate against Active Directory to log in.
We need to lock users out of AD after too many failed password attempts (number is set in domain policy).
As it works now, users get locked out of the application only but not out of AD. We need to lock them in AD and they will need to call help desk to unlock them.
I saw this http://msdn.microsoft.com/en-us/library/ms998360.aspx, where it is stated under "Account Lockout" that ActiveDirectoryMembershipProvider locks users out of provider but not out of AD.
But how do we lock users in AD then?
web.config:
<membership defaultProvider="MyADMembershipProvider">
<providers>
<add name="MyADMembershipProvider"
type="System.Web.Security.ActiveDirectoryMembershipProvider, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
connectionStringName="ADConnectionString"
connectionUsername="administrator"
connectionPassword="passw0rd"
attributeMapUsername="sAMAccountName" />
</providers>
</membership>
Login.aspx:
<asp:Login ID="Login1" runat="server" DisplayRememberMe="False" FailureText="Wrong user name or password." DestinationPageUrl="~/User.aspx" OnLoggedIn="Login1_LoggedIn" OnLoginError="Login1_LoginError">
Login.aspx.cs
protected void Login1_LoginError(object sender, EventArgs e)
{
string userName = Login1.UserName;
if (!string.IsNullOrEmpty(userName))
{
// Get information about this user
MembershipUser usr = Membership.GetUser(userName);
if (usr != null)
{
// check to see if the error occurred because they are not approved
if (!usr.IsApproved)
{
Login1.FailureText = "Your account has not yet been approved by an administrator.";
}
// check to see if user is currently locked out
else if (usr.IsLockedOut)
{
Login1.FailureText = "You have been locked out of your account due to too many failed passwords. Call help desk to unlock it.";
}
}
}
}

PayPal RestApiSDK .NET http 503 Server Unavailable

I am trying to use the PayPal .NET RestApiSDK to store credit cards and take payments in their sandbox. I am using .NET 4.5 in an MVC project.
I followed the example code here:
https://developer.paypal.com/webapps/developer/docs/api/#store-a-credit-card
Initially, things were very easy. On day one, I was able to:
-take several payments
-store several cards
-look up sales
-refund sales
-store cards in the vault
(basically, everything in their example code)
Ever since day one (about a week), I have been getting an http 503 "Server Unavailable" error. Unless I changed something in my sleep, I am using the exact code that worked before.
I contacted PayPal support, and after several back and forth messages they have let me know that while they can't pinpoint an error in my code, the error must be on my side, because their servers are working fine.
What is really strange, is that I seem to be able to do anything that doesn't change data. For instance, I can call payments.List(). However, I can't call creditCard.Create() or payment.Create().
Also, the access token is being created just fine. The line tokenCredential.GetAccessToken() does not return any server error. When I debug the code, it has indeed returned with a proper token.
Question:
What could possibly be causing an http 503 error when I try to store a card or take a payment?
Here is some relevant code.
controller:
public JsonResult RunTestPayment()
{
string id = ConfigManager.Instance.GetProperties()["ClientID"];
string secret = ConfigManager.Instance.GetProperties()["ClientSecret"];
OAuthTokenCredential tokenCredential = new OAuthTokenCredential(id, secret);
string accessToken = tokenCredential.GetAccessToken();
PayPal.Api.Payments.Address billingAddress = new PayPal.Api.Payments.Address();
billingAddress.line1 = "52 N Main St";
billingAddress.city = "Johnstown";
billingAddress.country_code = "US";
billingAddress.postal_code = "43210";
billingAddress.state = "OH";
PayPal.Api.Payments.CreditCard creditCard = new PayPal.Api.Payments.CreditCard();
creditCard.number = "4417119669820331";
creditCard.type = "visa";
creditCard.expire_month = 11;
creditCard.expire_year = 2018;
creditCard.cvv2 = "874";
creditCard.first_name = "Joe";
creditCard.last_name = "Shopper";
creditCard.billing_address = billingAddress;
PayPal.Api.Payments.Details amountDetails = new PayPal.Api.Payments.Details();
amountDetails.subtotal = "7.51";
amountDetails.tax = "0.03";
amountDetails.shipping = "0.03";
PayPal.Api.Payments.Amount amount = new PayPal.Api.Payments.Amount();
amount.total = "7.56";
amount.currency = "USD";
amount.details = amountDetails;
PayPal.Api.Payments.Transaction transaction = new PayPal.Api.Payments.Transaction();
transaction.amount = amount;
transaction.description = "This is the payment transaction description.";
List<PayPal.Api.Payments.Transaction> transactions = new List<PayPal.Api.Payments.Transaction>();
transactions.Add(transaction);
PayPal.Api.Payments.FundingInstrument fundingInstrument = new PayPal.Api.Payments.FundingInstrument();
fundingInstrument.credit_card = creditCard;
List<PayPal.Api.Payments.FundingInstrument> fundingInstruments = new List<PayPal.Api.Payments.FundingInstrument>();
fundingInstruments.Add(fundingInstrument);
PayPal.Api.Payments.Payer payer = new PayPal.Api.Payments.Payer();
payer.funding_instruments = fundingInstruments;
payer.payment_method = "credit_card";
PayPal.Api.Payments.Payment payment = new PayPal.Api.Payments.Payment();
payment.intent = "sale";
payment.payer = payer;
payment.transactions = transactions;
PayPal.Api.Payments.Payment createdPayment = payment.Create(accessToken);
return Json(new JsonWrapper { Data = createdPayment });
}
When stepping through, the error occors on the line
PayPal.Api.Payments.Payment createdPayment = payment.Create(accessToken);
the exact error (as a Json Object):
"ClassName":"PayPal.Exception.PayPalException","Message":"Exception in HttpConnection Execute: Invalid HTTP response The remote server returned an error: (503) Server Unavailable.","Data":null,"InnerException":{"ClassName":"PayPal.Exception.ConnectionException","Message":"Invalid HTTP response The remote server returned an error: (503) Server Unavailable.","Data":null,"InnerException":null,"HelpURL":null,"StackTraceString":" at PayPal.HttpConnection.Execute(String payLoad, HttpWebRequest httpRequest)","RemoteStackTraceString":null,"RemoteStackIndex":0,"ExceptionMethod":"8\nExecute\nPayPalCoreSDK, Version=1.4.1.0, Culture=neutral, PublicKeyToken=null\nPayPal.HttpConnection\nSystem.String Execute(System.String, System.Net.HttpWebRequest)","HResult":-2146233088,"Source":"PayPalCoreSDK","WatsonBuckets":null},"HelpURL":null,"StackTraceString":" at PayPal.PayPalResource.ConfigureAndExecute[T](Dictionary`2 config, IAPICallPreHandler apiCallPreHandler, HttpMethod httpMethod, String resourcePath)\r\n at PayPal.PayPalResource.ConfigureAndExecute[T](APIContext apiContext, HttpMethod httpMethod, String resource, String payload)\r\n at PayPal.Api.Payments.Payment.Create(APIContext apiContext)\r\n at PayPal.Api.Payments.Payment.Create(String accessToken)\r\n at Scout.Controllers.PaymentController.RequestPermissions() in e:\\Scout\\Scout\\Controllers\\PaymentController.cs:line 1105","RemoteStackTraceString":null,"RemoteStackIndex":0,"ExceptionMethod":"8\nConfigureAndExecute\nPayPalCoreSDK, Version=1.4.1.0, Culture=neutral, PublicKeyToken=null\nPayPal.PayPalResource\nT ConfigureAndExecute[T](System.Collections.Generic.Dictionary`2[System.String,System.String], PayPal.IAPICallPreHandler, PayPal.HttpMethod, System.String)","HResult":-2146233088,"Source":"PayPalCoreSDK","WatsonBuckets":null
web.config (api keys are truncated here):
...
<configuration>
<configSections>
<section name="paypal" type="PayPal.Manager.SDKConfigHandler, PayPalCoreSDK" />
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
...
</configSections>
...
<paypal>
<settings>
<add name="endpoint" value="https://api.sandbox.paypal.com"/>
<add name="ClientID" value="AbayoRB3Eq6YxM6"/>
<add name="ClientSecret" value="EDWNfxDxnGZ3hWZW"/>
<add name="connectionTimeout" value="360000"/>
<!-- The number of times a request must be retried if the API endpoint is unresponsive -->
<add name="requestRetries" value="3"/>
</settings>
</paypal>
...
<log4net>
<appender name="FileAppender" type="log4net.Appender.FileAppender">
<file value="ScoutPaypalLog.log" />
<appendToFile value="true" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] %message%newline" />
</layout>
</appender>
<root>
<level value="DEBUG" />
<appender-ref ref="FileAppender" />
</root>
</log4net>
As you can see, I have configured log4net, and it is recording data generated by another .dll I'm using (for RavenDB), but there are no entries made by PayPal.
Thanks!
I finally uninstalled the two nuget packages RestApiSDK and PayPalCoreSDK. I then restarted Visual Studio. Finally, I re-installed those same two packages.
Without changing any code, it started working.

Authorization denied message with FormsAuthentication

So, I've implemented my IPrincipal.IsInRole(...) and I'm using FormsAuthentication like so:
<authentication mode="Forms">
<forms loginUrl="Login.aspx" name="someName" timeout="600"/>
</authentication>
Then I have a page that requires you to be authenticated and that you have "roleA". This is configured like so:
<location path="SomePage.aspx">
<system.web>
<authorization>
<allow roles="roleA" />
<deny users="*"/>
</authorization>
</system.web>
</location>
Now, I login to my web application, but with a user that does NOT have roleA. When I visit SomePage.aspx I get redirected to Login.aspx, the url specified in loginUrl of the forms element. So, my question is shouldn't I be able be specify an authorization denied message or url? If the user is authenticated, but not authorized why would I want to redirect to the login page. It's confusing as hell to the user. Please tell me I am missing something simple.
Thanks for reading!
Yeah, this is a little annoying. Maybe someone has a simpler idea, but the solution (hack?) that we came up with was to look for the originally-requested URL that ASP.NET appends to the query string when the user is redirected to the login page.
We created a new web.config section that stores a set of keys/values that match a fragment of the redirect URL to an authorization message:
<configSections>
<section name="authorizationFailureMessages" type="System.Configuration.NameValueSectionHandler, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
...etc...
</configSections>
<authorizationFailureMessages>
<add key="MemberResources" value="MembershipRequired" />
<add key="Staff" value="StaffOnly" />
<add key="Departments/Administration/BoardOfDirectors" value="BoardOfDirectorsOnly" />
...etc...
</authorizationFailureMessages>
In the Page_Load() event of the Login.aspx page, we call a method that uses this URL to determine which (un)authorization event occurred then redirect them to a message page that displays the appropriate text:
private void DisplayAppropriateAuthorizationMessage ()
{
if ( !Page.User.Identity.IsAuthenticated )
return;
string redirectUrl = FormsAuthentication.GetRedirectUrl( Page.User.Identity.Name, false );
if ( string.IsNullOrEmpty( redirectUrl ) )
return;
NameValueCollection authorizationFailureMessages = ConfigurationManager.GetSection( "authorizationFailureMessages" ) as NameValueCollection;
if ( authorizationFailureMessages == null )
return;
foreach ( string key in authorizationFailureMessages.AllKeys )
{
if ( redirectUrl.Contains( key ) )
{
Response.Redirect( String.Format( "Message.aspx?{0}={1}", Constants.QueryStringKeys.ERRORMESSAGENAME, authorizationFailureMessages[ key ] ), true );
}
}
}
Roles.IsUserInRole - If you're just using it for this page, throw this in the code behind. If you have a lot of pages, you could consider putting this in a base class and reading either from the web.config or the database per page. I believe this will give you the most control.
I basically agree to #MattPeterson 's solution. But I suggest two improvements.
In my view, you just tell that "according to the roles that you are, you are not allowed to visit that page", that is enough. You do not need to tell which extra roles are needed, which will expose the details of authorization management of your website.
You can get access control list from web.config (in each folder), and no need to write <add key="MemberResources" value="MembershipRequired" /> again.
I believe you should have something similar to
<authorization>
<deny users="?" />
</authorization
in your web.config.

Resources