My page hides content if the user isn't in a role that should see it.
Currently I have 2 roles that can see this content: global and admin.
If the user is in either of these roles, they should be able to see the content, but I'm having trouble working out the condition.
Here's what I've got so far:
// roles can be a single role ("global")
// or it can be multiple roles ("global,admin")
private bool CheckAllowed(string roles)
{
var user = HttpContext.Current.User;
var allowed = false;
if (roles.Contains(","))
{
string[] rolesArr = roles.Split(',');
foreach (string r in rolesArr)
{
allowed = (user.IsInRole(r)) ? true : false;
}
}
else
{
allowed = (user.IsInRole(r)) ? true : false;
}
return allowed;
}
My issue is with the loop.
If the user is in rolesArr[0] but not in rolesArr[1] then the loop will mark them "not allowed" and they won't see the content they need to as a member of rolesArr[0].
How can I track whether or not a user is allowed to view the role-specific content more accurately?
If the user is in rolesArr[0] but not in rolesArr[1] then the loop will mark them "not allowed" and they won't see the content they need to as a member of rolesArr[0].
Then what you're checking if it's user has any of those roles. There is a LINQ method for this:
bool allowed = rolesArr.Any(x => user.IsInRole(x));
In your code with:
foreach (string r in rolesArr)
allowed = (user.IsInRole(r)) ? true : false;
You were just checking if user is in the last role of the list because each iteration will overwrite previous result (also note that this expression may be simplified to allowed = user.IsInRole(r)).
That said, note that you might simplify your code. First of all you do not need to check if string is a list or not, always Split() the string: performance impact is so negligible that it's not worth your effort. In short your code may be:
private bool CheckAllowed(string roles)
=> roles.Split(',').Any(x => HttpContext.Current.User.IsInRole(x));
Just for reference in case someone else will need it, if you need to check if user is in every role you can use All() instead of Any(). How it looks like without LINQ? Simply:
private bool CheckAllowed(string roles)
{
foreach (var role in roles.Split(','))
{
if (HttpContext.Current.User.IsInRole(x))
return true;
}
return false;
}
Related
So in my Discord bot, I am creating a full moderation system where users with appropriate privileges may hand out warnings to other users, these will be recorded, once 3 warnings are hit, the user is muted for a set time, this can occur 5 times for which they are muted for longer periods of time, after the fifth mute when the user reaches 3 more warnings, they are banned permanently. So I have pretty much made the whole thing which was no issue with a history viewer of users etc, but the problem I am having is with comparing roles. I have found a solution that I am not sure if it works properly in the long run, but was the only method I could think of. I want it to not allow users to warn those with higher ranks than themselves. This is the code I have for it:
public class Warn : ModuleBase<SocketCommandContext>
{
[Command("Warn")]
public async Task WarnMain([Remainder, Summary("Warn a user for a reason.")] IGuildUser user, string warnInfo)
{
var userRole = -1;
var victimRole = -1;
var counter = 0;
foreach(var role in Context.Guild.Roles)
{
if (Context.Guild.GetUser(Context.User.Id).Roles.Contains(role) && userRole == -1)
{
userRole = counter;
}
if (Context.Guild.GetUser(user.Id).Roles.Contains(role) && victimRole == -1)
{
victimRole = counter;
}
}
if (userRole < victimRole)
// blah blah
}
}
If you know of a better way or more efficient way of doing this, please share, would be much appreciated. Thanks.
First off, I would like to point out that discord.py has their own method of comparing between permissions. You could try to run their python script in your C# code, or 'translate' it from python into C#. You can check out their source code for comparing between permissions here.
For me personally, since my bot is not really made for moderation, I took the lazy way out and made a simple function that compares permissions using nested if-else statements.
private static bool PermissionComparison(GuildPermissions targetGuildPerms, GuildPermissions userGuildPerms)
{
//True if the target has a higher role.
bool targetHasHigherPerms = false;
//If the user is not admin but target is.
if(!userGuildPerms.Administrator && targetGuildPerms.Administrator) {
//The target has higher permission than the user.
targetHasHigherPerms = true;
} else if(!userGuildPerms.ManageGuild && targetGuildPerms.ManageGuild) {
targetHasHigherPerms = true;
} else if(!userGuildPerms.ManageChannels && targetGuildPerms.ManageChannels) {
targetHasHigherPerms = true;
} else if(!userGuildPerms.BanMembers && targetGuildPerms.BanMembers) {
targetHasHigherPerms = true;
} else if(!userGuildPerms.KickMembers && targetGuildPerms.KickMembers) {
targetHasHigherPerms = true;
}
return targetHasHigherPerms;
}
I would personally suggest using other methods instead if they seem better.
It seems the way I did it is the only way to actually compare roles as far as I can tell. Spent the last 4 hours looking for alternatives but did not come across anything.
My method is simply going through each role and comparing, I believe the API already sorts the roles from highest first to lowest last. You can then use a counter to compare the numbers at which the highest role is found that is assigned the the user.
Comparing permissions doesn't always work, especially in my case, therefore I could not use that method either. This seems to work best for me. If anyone has anything better feel free to share, I'd love to see how I can improve it.
If you cast the user to a SocketGuilderUser then you can compare the Hierarchy property.
Its basically the same thing you are already doing just avoids looping through the roles.
I have read the relevant Stack Overflow questions and tried out the following code:
WindowsIdentity identity = WindowsIdentity.GetCurrent();
if (null != identity)
{
WindowsPrincipal principal = new WindowsPrincipal(identity);
return principal.IsInRole(WindowsBuiltInRole.Administrator);
}
return false;
It does not return true even though I have manually confirmed that the current user is a member of the local built-in Administrators group.
What am I missing ?
Thanks.
Just found other way to check if user is admin, not running application as admin:
private static bool IsAdmin()
{
WindowsIdentity identity = WindowsIdentity.GetCurrent();
if (identity != null)
{
WindowsPrincipal principal = new WindowsPrincipal(identity);
List<Claim> list = new List<Claim>(principal.UserClaims);
Claim c = list.Find(p => p.Value.Contains("S-1-5-32-544"));
if (c != null)
return true;
}
return false;
}
Credit to this answer, but code is corrected a bit.
The code you have above seemed to only work if running as an administrator, however you can query to see if the user belongs to the local administrators group (without running as an administrator) by doing something like the code below. Note, however, that the group name is hard-coded, so I guess you would have some localization work to do if you want to run it on operating systems of different languages.
using (var pc = new PrincipalContext(ContextType.Domain, Environment.UserDomainName))
{
using (var up = UserPrincipal.FindByIdentity(pc, WindowsIdentity.GetCurrent().Name))
{
return up.GetAuthorizationGroups().Any(group => group.Name == "Administrators");
}
}
Note that you can also get a list of ALL the groups the user is a member of by doing this inside the second using block:
var allGroups = up.GetAuthorizationGroups();
But this will be much slower depending on how many groups they're a member of. For example, I'm in 638 groups and it takes 15 seconds when I run it.
Today I came across a problem: I was trying to check the errors of a software in order to provide the right behavior of the program when it incurs in the error.
I had to check if a user already exists in the database.
The problem is that the back-end doesn't provide an errorId so I have to check the errors by the text.
Errors are displayed as this:
The user Name already Exists!
The Switch statement is this:
switch (error.text)
{
case "User Test already exists":
Console.WriteLine("The user already Exists"); //this is a test behaviour.
break;
default:
Console.WriteLine("I couldn't behave in any way :<");
}
As you can imagine the names are all different (it's a unique field in the DB), so the word "Test" in the case statement should be the name of the user.
Can I dynamically change the string?
Seems like a Regex would do the trick. I've built this Regex based off the pattern:
The user Name already Exists!
where Name can be any value. The Regex is:
(the user .* already exists)
To use it you'll do something like this:
Regex.IsMatch(error.text, "(the user .* already exists)", RegexOptions.IgnoreCase)
Which would return a true or false based on the match. Now, this can't be done in the switch, but you could just run the value through a number of Regexes to determine which it matched. One thing you might consider is an extension method. Consider this one:
public static class RegexExtensions
{
private static readonly Regex UserNameAlreadyExists = new Regex("(the user .* already exists)", RegexOptions.IgnoreCase | RegexOptions.Compiled);
public static bool IsUserNameAlreadyExists(this string inputValue)
{
return UserNameAlreadyExists.IsMatch(inputValue);
}
}
The usage for this would be really nice:
if (error.text.IsUserNameAlreadyExists())
{
// do something
}
The extension method is a really nice way of working through it. It's fully encapsulated and would keep the usage really clean. Furthermore, it's easier to define the Regex in one place and thus set it to Compiled making it faster.
Preferably change the back-end or have it changed (it definitely should return some sort of error code instead of an already localized message obviously meant to be shown to the user - that's clearly a front-end task).
To answer the question, no; consider using something like this instead (original phrasing, be aware that these string comparisons are case sensitive):
if(error.text.StartsWith("User ") && error.text.EndsWith(" already Exists"))
{
Console.WriteLine("The user already Exists"); //this is a test behaviour.
}
else
{
Console.WriteLine("I couldn't behave in any way :<");
}
I suppose this would be a fairly simple solution:
class Program
{
int errorIndex = 5; //Based on error expected text. Can add more criteria here.
private static bool testResponse = false;
static void Main(string[] args)
{
string text = "The user already exists";
getErrorMessage(text);
}
private static void getErrorMessage(string message)
{
var user = message.Substring(4, 4);
var exists = message.Substring(17, 6);
if (user == "user" && exists == "exists")
//Write the error message.
Console.WriteLine(message.ToString());
var errorMessage = message;
if (errorMessage != null)
{
testResponse = true;
}
Console.ReadLine();
}
}
This is if you know the exact location of the length and index of certain words of the error message. You could use that information to further narrow down the errors you expect. This is assuming that there is no errorId.
just to be sure: The back-end doesn't provide any errorID? If you use C# for the database connection (i.e. ADO.Net) you have possibilitys for efficent error handling.
Is it possible to just check if error.text is empty or not?
if(error.text=="")Console.WriteLine("The User already exists");
else Console.WriteLine("I couldn't behave in any way");
If you want to check if there are duplicates in the "user" column you could check the database directly via SQL.
We're building an ASP.NET app, and have a requirement to use the corporate LDAP system (Siteminder) for authentication (upside: no login dialogs). Roles are created in the LDAP tool, and users are assigned to the roles by userland managers (read: the structure has to be easily understood). Currently, all apps that use the system use a dual-entry process whereby the roles identified in the app are hand-entered into the LDAP system and users are assigned, then app functions are assigned to their role mirrors in an app-based control panel. This works, but it bothers me that dual-entry is required.
What I would like to achieve is something where the app queries the LDAP system to get a list of roles that are assigned to the app (which is identified in the LDAP system) and populate the role:function control panel with them. This part seems really straightforward. However, I lose clarity when it comes to figuring out what to put in the Authorize attribute:
[Authorize(Roles = "Admin, Moderator")]
would become... what?
[Authorize(LoadedRoles(r => r.FindAll("some expression that describes the roles that have a particular permission")))]
I'm seriously into blue sky territory here. I read this question, and liked - from an architectural standpoint - the answer that suggested making the permissions the roles. But that might not be acceptable to the userland managers that needed to manage users. On the other hand, this question turns things into non-string resources, but I can't conceive of how to translate that into "roles that have this sort of function included".
Any suggestions?
Update:
Based on the advice of #venerik below, I've made some progress. For the time being, I'm encapsulating everything in the [AuthorizeFunctionAttribute], and will farm the individual pieces out where they belong later. To that end, I created three variables:
private IList<KeyValuePair<long, string>> Roles;
private IList<KeyValuePair<long, string>> Functions;
private IList<RoleFunction> RoleFunctions;
...then put static data in them:
Roles = new ICollection<KeyValuePair<long, string>>();
Roles.Add(KeyValuePair<long, string>(1, "Basic User"));
Roles.Add(KeyValuePair<long, string>(2, "Administrator"));
Functions = new ICollection<KeyValuePair<long, string>>();
Functions.Add(KeyValuePair<long,string>(1,"List Things"));
Functions.Add(KeyValuePair<long,string>(2,"Add Or Edit Things"));
Functions.Add(KeyValuePair<long,string>(3,"Delete Things"));
...and finally bound them together (in a complicated manner that lays the groundwork for the future):
RoleFunctions = new IList<RoleFunction>();
RoleFunctions.Add(
new RoleFunction
{
RoleId = Roles.Where( r => r.Value == "Basic User").FirstOrDefault().Key,
FunctionId = Functions.Where( f => f.Value == "List Things" ).FirstOrDefault().Key,
isAuthorized = true
},
new RoleFunction
{
RoleId = Roles.Where( r => r.Value == "Administrator").FirstOrDefault().Key,
FunctionId = Functions.Where( f => f.Value == "Add or Edit Things" ).FirstOrDefault().Key,
isAuthorized = true
},
// More binding...
);
I feel good about this so far. So I went researching AuthorizeCore to see what I needed to do there. However, per the comment at the bottom of the page, it's not very helpful. I more or less get that at the end, the method needs to return a bool value. And I get that I need to check that one of the User.Roles array fits the permission that's passed in through [AuthorizeFunction("List Things")].
Update (again):
I've got the following code, which seems like it will do what I need (one method needs fleshing out):
/// <summary>An authorization attribute that takes "function name" as a parameter
/// and checks to see if the logged-in user is authorized to use that function.
/// </summary>
public class AuthorizeFunctionAttribute : AuthorizeAttribute
{
private IList<KeyValuePair<long, string>> Roles;
private IList<KeyValuePair<long, string>> Functions;
private IList<RoleFunction> RoleFunctions;
public string Function { get; private set; }
public AuthorizeFunctionAttribute(string FunctionName)
{
Function = FunctionName;
Roles = SetApplicationRoles();
Functions = SetApplicationFunctions();
RoleFunctions = SetRoleFunctions();
}
protected virtual bool AuthorizeCore(HttpContextBase httpContext)
{
bool userIsAuthorized = false;
foreach (string ur in GetUserRoles(httpContext.Current.Request.Headers["SM_USER"]))
{
long roleId = Roles.Where( sr => sr.Value == ur )
.First().Key;
long functionId = Functions.Where( sf => sf.Value == Function )
.First().Key;
// If any role is authorized for this function, set userIsAuthorized to true.
// DO NOT set userIsAuthorized to false within this loop.
if (RoleFunctions.Where(rf => rf.RoleId == roleId && rf.FunctionId == functionId)
.First().isAuthorized)
{
userIsAuthorized = true;
}
}
return userIsAuthorized;
}
Previously I didn't know enough about the underlying bits of creating a custom attribute to get out of my own way. However, this MSDN article told me what should have been obvious to me in the beginning: build it yourself. So, once I get the GetUserRoles() method put together, I should be underway.
I think you can solve this using a custom AuthorizeAttribute. In a project I worked close to they used that to access Active Directory (as described in this answer).
In your case it would look something like:
public class AuthorizeWithLDAPAttribute(string functionName) : AuthorizeAttribute
{
protected virtual bool AuthorizeCore(HttpContextBase httpContext)
{
// check LDAP to verify that user has
// a role that's linked to `functionName`
}
}
Next you can use this attribute on your controllers and/or methods:
[AuthorizeWithLDAP("functionName1")]
public class BlogController : Controller
{
....
[AuthorizeWithLDAP("functionName2")]
public ViewResult Index()
{
return View();
}
}
The controller is now only accessible to users whose role are linked to functionName1 and the method is only accessible to users whose role are linked to functionName1 and functionName2
How do I check in C# what the current users role is, and print it to the screen.
Thanks!
You can use Roles.GetRolesForUser() method to get all the rols user belong to . use it like this;
string[] rolesuserbelongto = Roles.GetRolesForUser();
you will have all roles in string array.
you can even pass a UserName as a parameter to get the roles for that particular User like this:
string[] rolesuserbelongto = Roles.GetRolesForUser("Shekhar_Pro");
The most general method is to get an IPrinciple and then call IsInRole() on it. How you get the Principle denpends on your runtime environment. This example works well for apps running under the user's account.
Example:
static void PrintIsInAdministrators()
{
// There are many ways to get a principle... this is one.
System.Security.Principal.IPrincipal principle = System.Threading.Thread.CurrentPrincipal;
bool isInRole = principle.IsInRole("MyDomain\\MyRole");
Console.WriteLine("I {0} an Admin", isInRole ? "am" : "am not");
}
Roles.GetRolesForUser(); gave me the error The Role Manager feature has not been enabled.
If you are using ASP.NET Identity UserManager you can get it like this:
var userManager = Request.GetOwinContext().GetUserManager<ApplicationUserManager>();
var roles = userManager.GetRoles(User.Identity.GetUserId());
If you have changed key for user from Guid to Int for example use this code:
var roles = userManager.GetRoles(User.Identity.GetUserId<int>());
string[] userroles = Roles.GetRolesForUser(Page.User.Identity.Name);
foreach(var role in userroles)
{
Response.Write(role);
}
This is what you are looking for:
#if (Request.IsAuthenticated)
{
if (User.IsInRole("Admin"))
{
<h1> I only show this text to admin users </h1>
}
}
Note: You can check the roles defined in your AccountController.cs file, if you have one implemented.
You can use user manager for that purpose:
var userRoles = await _userManager.GetRolesAsync(user);
Not entirely sure of you question.
You can do:
this.User.IsInRole();
//loop and check whether the user is in your role.
this would correspond to a page class, so you can write the above code only inside a page and this.User returns an IPrincipal.