I have an MVC 5 data first app that I've been helping with. I made it use a filter to determine if the AD user is allowed certain functionality (via decorating methods/classes).
However, after changing a user's info in the db, the filter's context still has the old info for that user. The user 'edit' page keeps correct info though. Strange. This is happening just in the filter, not in any controller.
Here's the filter:
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (!base.AuthorizeCore(httpContext)) return false;
// Get the groups for this authorization (from the decoration attribute)
var groups = Groups.Split(',').ToList();
var userDisplayName = string.Empty;
using (HostingEnvironment.Impersonate())
{
// Verify that the user is in the given AD group (if any)
var context = new PrincipalContext(
ContextType.Domain);
var userPrincipal = UserPrincipal.FindByIdentity(
context,
IdentityType.SamAccountName,
httpContext.User.Identity.Name);
userDisplayName = userPrincipal.DisplayName;
}
// check for UserRoles for this
var user = _varDB.Users.FirstOrDefault(x => x.UserName == userDisplayName); //< Here is where it gets stale info...
IList<UserRole> usersRoles = new List<UserRole>();
if (user != null)
{
usersRoles = user.UserRoles.ToList();
}
// determine if the user is allowed to see this controller/page
foreach (var ur in usersRoles)
{
if (groups.Contains(ur.RoleName))
{
return true;
}
}
return false;
}
On the UserController, if I manually change the db, and refresh the page, I see the changes. But in this filter, if I change the data, it never changes until I recycle the app pool.
fyi, the top of the class goes like this:
public class AuthorizeByDbRoleAttribute : AuthorizeAttribute
{
private static ExtVariablesEntities _varDB = new ExtVariablesEntities();
}
If this filter doesn't get the updated user info, it can't keep users out of or let them use functionality if changed and the app pool isn't recycled.
Problem was that a class that inherits from AuthorizeAttribute (or any attribute I believe) the class doesn't dispose of the context. I wound up wrapping the logic in a using like this:
using (ExtVariablesEntities _varDB = new ExtVariablesEntities())
{
// check for UserRoles for this
var user = _varDB.Users.FirstOrDefault(x => x.UserName == userDisplayName);
IList<UserRole> usersRoles = new List<UserRole>();
if (user != null)
{
usersRoles = user.UserRoles.ToList();
}
// determine if the user is allowed to see this controller/page
foreach (var ur in usersRoles)
{
if (groups.Contains(ur.RoleName))
{
return true;
}
}
}
This was the context get created each time this class method is hit.
Related
I want to make my discord bot respond exclusively to people with a #Member role, so when a person without that role writes a command (ex. >say Hello), the bot will not respond to it, but when a person with the #Member role writes that, it will.
Ideally, if this is being used for a command or rather multiple commands, a precondition should be used (example administration commands). This allows you to not have to duplicate the checks within each command.
Ah example of such a precondition can be found here.
You can read more on preconditions here
You will need to add role validation to each of your commands.
The quick and easy way would be to do the following:
[Command("test")]
public async Task TestCommand()
{
var user as Context.User as IGuildUser;
var roleToValidate = Context.Guild.Roles.First(r => r.Name == "SomeRoleName");
if (!user.RoleIDs.Any(r => r == roleToValidate.Id))
return;
// the rest of the code
}
Another approach (which I would recommend) is using PreconditionAttribute
/// CheckRole.cs
using Discord;
using Discord.Commands;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Example.Attributes
{
public class CheckRole : PreconditionAttribute
{
private List<string> _roles;
public CheckRole(params string[] roles)
{
_roles = roles.ToList();
}
public async override Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo command)
{
var user = context.User as IGuildUser;
var discordRoles = context.Guild.Roles.Where(gr => _roles.Any(r => gr.Name == r));
foreach (var role in discordRoles)
{
var userInRole = user.RoleIds.Any(ri => ri == role.Id);
if (userInRole)
{
return await Task.FromResult(PreconditionResult.FromSuccess());
}
}
return await Task.FromResult(PreconditionResult.FromError("You do not have permission to use this role."));
}
}
}
/// WhateverClassThatYouWriteCommandsIn.cs
[Command("test")]
[CheckRole("AdminRoleName", "ModeratorRole", "NormalRole")]
public async Task TestCommandForMostRoles()
{
var user as Context.User as IGuildUser;
var roleToValidate = Context.Guild.Roles.First(r => r.Name == "Some Role Name");
if (!user.RoleIDs.Any(r => r == roleToValidate.Id))
return;
// the rest of the code
}
[Command("test")]
[CheckRole("AdminRoleName", "ModeratorRole")]
public async Task TestCommandForAdmins()
{
var user as Context.User as IGuildUser;
var roleToValidate = Context.Guild.Roles.First(r => r.Name == "Some Role Name");
if (!user.RoleIDs.Any(r => r == roleToValidate.Id))
return;
// the rest of the code
}
This literal code may not work as I haven't tested it, however it is based on my own implementation of role preconditions authorisation which works.
To break down the code:
I have a variable to store multiple role names and use params[] in the constructor to allow any amount of role names to be provided. They are stored in the variable.
private List<string> _roles;
public CheckRole(params string[] roles)
{
_roles = roles.ToList();
}
CheckPermissionsAsync is automatically called every time that particular command is called.
public async override Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo command)
Get the actual Role objects from the context from the names, loop through them and check the user to see if they have that permission. The first time a role is found on a user it will return a success and the command code in the original command function will be run. If FromError is returned then the command code is not run.
var user = context.User as IGuildUser;
var discordRoles = context.Guild.Roles.Where(gr => _roles.Any(r => gr.Name == r));
foreach (var role in discordRoles)
{
var userInRole = user.RoleIds.Any(ri => ri == role.Id);
if (userInRole)
{
return await Task.FromResult(PreconditionResult.FromSuccess());
}
}
return await Task.FromResult(PreconditionResult.FromError("You do not have permission to use this role."));
This might seem like a lot, but you do not need to re-write role authorisation code again and you can simply add this attribute to whatever commands you want. You can also add this attribute to the class if you want every command in that class to be authorised by the role:
[CheckRoles("Moderator", "LowLevelModerator")]
public class ModeratorCommands : ModuleBase<SocketCommandContext>
{
[Command("CheckStats")]
public async Task ModeratorCommandForCheckStats()
{
// the code
}
[Command("BanUser")]
public async Task ModeratorCommandForBanUser()
{
// the code
}
[CheckRole("Admin")]
[Command("BanUser")]
public async Task ModeratorCommandOnlyForAdminsForBanModerator()
{
// the code
}
}
I've been working on, you can only log in to their profile one of time. There, I think that, for example, must not only be for example, userid = 1 twice at the same time.
It should be such that this option will only be assigned to a user and it is him / her who log in first at the single user ID which is assigned.
The file will be made by Global.asax.cs file.
Right now it's possible that only log in multiple simultaneous ID.
public class SessionExpireAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
HttpContext ctx = HttpContext.Current;
// check sessions here
string UserIdText = "Userid";
if (ctx.Session[UserIdText] != null)
{
using (var DB = new ordklarEntitiesDatabase())
{
var sessionId = ctx.Session.SessionID;//unique sessionID
var UserId = ctx.Session[UserIdText].ToString().ToInt();//Userid session
var sessioncheck = DB.SessionId.FirstOrDefault(i => i.SessionIdValue == sessionId && i.Userid == UserId);
if (sessioncheck == null)
{
ctx.Session[UserIdText] = null;
filterContext.Result = new RedirectResult("~/Account/login");
return;
}
}
}
base.OnActionExecuting(filterContext);
}
}
In our server, CORS is already enabled, so that scripts like ajax may communicate in our API. But this is only effective on API that has no SecureAttribute
This one is working properly:
[CorsPreflightEnabled]
public class DevicesController : ApiController
{
[CorsEnabled]
[HttpPost]
public bool Register(DTO::ClientInfo info)
While this one is always rejected:
[CorsPreflightEnabled]
[Http::Secure]
public class UserController : ApiController
{
[CorsEnabled]
[HttpPost]
public bool AddClaims(Domain::DTO.UserClaim claim)
This is the code for SecureAttribute:
public class SecureAttribute : AuthorizeAttribute
{
protected override bool IsAuthorized(System.Web.Http.Controllers.HttpActionContext actionContext)
{
var calltoken = HttpContext.Current.Request.Headers["Device"] ?? "";
var token = JsonConvert.DeserializeObject<DeviceCallToken>(calltoken) ?? new DeviceCallToken();
var cachetoken = new ClientAuthentication().VerifyDevice(token);
if (cachetoken != null)
{
// if a cachetoken was successfully extracted from our records,
// then store the information into the principal for possible reuse
var principal = AppPrincipal.Current;
var identity = principal.Identity as AppIdentity;
identity.ServiceHeader.SessionId = token.SessionId;
identity.ServiceHeader.ClientKey = cachetoken.ClientKey;
identity.ServiceHeader.DeviceCode = cachetoken.DeviceCode;
identity.ServiceHeader.Merchant = cachetoken.Merchant;
Thread.CurrentPrincipal = principal;
HttpContext.Current.User = principal;
}
return cachetoken != null && !string.IsNullOrWhiteSpace(cachetoken.Salt);
}
}
When I call the API using ajax the Method is set to OPTIONS and the value of calltoken is always null.
Now my question is, how can I bypass checking the security when the method is OPTIONS?
I found that, if I try to put values in calltoken via breakpoint, the IsAuthorized will be called again for the last time, and from there the Device header has now value.
I really hope that I explained myself well. If not, I may have to show some images.
EDIT: Working code
public class SecureAttribute : AuthorizeAttribute
{
protected override bool IsAuthorized(System.Web.Http.Controllers.HttpActionContext actionContext)
{
var calltoken = HttpContext.Current.Request.Headers["Device"] ?? "";
var token = JsonConvert.DeserializeObject<DeviceCallToken>(calltoken) ?? new DeviceCallToken();
var cachetoken = new ClientAuthentication().VerifyDevice(token);
if (cachetoken != null)
{
// if a cachetoken was successfully extracted from our records,
// then store the information into the principal for possible reuse
var principal = AppPrincipal.Current;
var identity = principal.Identity as AppIdentity;
identity.ServiceHeader.SessionId = token.SessionId;
identity.ServiceHeader.ClientKey = cachetoken.ClientKey;
identity.ServiceHeader.DeviceCode = cachetoken.DeviceCode;
identity.ServiceHeader.Merchant = cachetoken.Merchant;
Thread.CurrentPrincipal = principal;
HttpContext.Current.User = principal;
}
else
{
var originalRequest = actionContext.Request;
var isCorsRequest = originalRequest.Headers.Contains("Origin");
if (originalRequest.Method == HttpMethod.Options && isCorsRequest)
{
// Allow to penetrate
return true;
}
}
return cachetoken != null && !string.IsNullOrWhiteSpace(cachetoken.Salt);
}
}
There is no way to bypass the authorisation attribute, that would be pretty insecure if it were possible. Your options are:
Remove the attribute from the methods you need to call.
Pass the correct security headers in the ajax call.
Modify the SecureAttribute class to allow OPTIONS method (but that sounds pretty insecure too)
Create new methods for ajax that don't have the attribute attached.
I'd suggest option 2.
I am new to Nancy and .NET framework. here I am running in a panic situation.
I have written my own Bootstrapper to enable CookieBasedSessions in project.Here are tasks i have to perform.
Once user logged in create session and set User in session.
Then on each request verify the user in session
class Bootstrapper : DefaultNancyBootstrapper
{
protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines)
{
base.ApplicationStartup(container, pipelines);
CookieBasedSessions.Enable(pipelines);
}
}
Here is my controller.
Post["/myapp/login"] = parameters =>
{
var model = new User();
model.UserName = this.Request.Form.userName;
model = manager.GetUser(model.UserName);
if (!string.IsNullOrEmpty(model.UserName) && model.isAdmin())
{
// var user = Request.Session["user"];
if (Request.Session["key"] != null) // Here i get the ERROR
Request.Session["key"] = model;
return Response.AsRedirect(HOME_URL);
}
else
return Response.AsRedirect(LOGIN_URL);
};
when I execute this code I am getting {"Session support is not enabled."} error.
Any help is appreciated.
It's not using your custom bootstrapper - make the class public and it will pick it up.
I have the following login method in my MVC project
public ActionResult Login(LoginModel model, string returnUrl)
{
if (ModelState.IsValid)
{
if (_authenticationRepository.Login(model.UserName, model.Password))
{
var authenticationTicket = new FormsAuthenticationTicket(1, model.UserName, DateTime.Now,
DateTime.Now.AddMinutes(20),
model.RememberMe, "", "/");
var cookie = new HttpCookie(FormsAuthentication.FormsCookieName,
FormsAuthentication.Encrypt(authenticationTicket));
Response.Cookies.Add(cookie);
UserContext.CreateUserContext();
return RedirectToLocal(returnUrl);
}
}
UserContext.cs
This stores the user/permissions into a session.
public static void CreateUserContext()
{
BuildUserContext(HttpContext.Current.User);
}
private static void BuildUserContext(IPrincipal principalUser)
{
if (!principalUser.Identity.IsAuthenticated) return;
var user = _userAccountsRepository.GetUserByUserName(principalUser.Identity.Name);
if (user == null) return;
var userContext = new UserContext { IsAuthenticated = true };
var siteUser = Mapper.Map(user);
userContext.SiteUser = siteUser;
HttpContext.Current.Session["UserContext"] = userContext;
}
I am aware that IsAuthenticated will only become true after a redirect. So within my login() method, is it possible to ensure that principalUser.Identity.IsAuthenticated will return true?
Or where else will be a good place to create the user context if not it the login method?
What I'm trying to achieve is:
user logs in
if login is successful, query db for his roles/permissions and save them into a session so that I don't have to requery every time I'm checking if the user has access to a certain action.
You could do something like follows:
When user logs in for the first time, get user's role/permission details, serialize it and store it in the session. So as this session is in the memory, every time you want to check if user has permission to an operation deserialize this from memory instead of going to the database.