Symfony 2.1 - $this->get('security.context')->isGranted('ROLE_ADMIN') returns false even if Profiler says I have that role - symfony-2.1

I have a Controller action (the Controller has $this->securityContext set to $this->get('security.context') via JMSDiExtraBundle):
$user = $this->securityContext->getToken()->getUser();
$groupRepo = $this->getDoctrine()->getRepository('KekRozsakFrontBundle:Group');
if ($this->securityContext->isGranted('ROLE_ADMIN') === false) {
$myGroups = $groupRepo->findByLeader($user);
} else {
$myGroups = $groupRepo->findAll();
}
When I log in to the dev environment and check the profiler, I can see that I have the ROLE_ADMIN role granted, but I still get the filtered list of Groups.
I have put some debugging code in my Controller, and Symfony's RoleVoter.php. The string representation of the Token in my Controller ($this->securityContext->getToken()) and the one in RoleVoter.php are the same, but when I use $token->getRoles(), I get two different arrays.
My Users and Roles are stored in the database via the User and Role entities. Is this a bug that I found or am I doing something wrong?

Finally got it. A dim idea hit my mind a minute ago. The problem was caused my own RoleHierarchyInterface implementation. My original idea was to copy Symfony's own, but load it from the ORM instead of security.yml. But because of this, I had to totally rewrite the buildRoleMap() function. The diff is as follows:
private function buildRoleMap()
{
$this->map = array();
$roles = $this->roleRepo->findAll();
foreach ($roles as $mainRole) {
$main = $mainRole->getRole();
- $this->map[$main] = array();
+ $this->map[$main] = array($main);
foreach ($mainRole->getInheritedRoles() as $childRole) {
$this->map[$main][] = $childRole->getRole();
// TODO: This is one-level only. Get as deep as possible.
// BEWARE OF RECURSIVE NESTING!
foreach ($childRole->getInheritedRoles() as $grandchildRole) {
$this->map[$main][] = $grandchildRole->getRole();
}
}
}
}

This case - roles are set and are displayed in Symfony's profiler but isGranted returns false - can be happened when the role names does not start with prefix ROLE_.
Bad role name: USER_TYPE_ADMIN
Correct role name: ROLE_USER_TYPE_ADMIN

Related

Best approach for loading Kentico 10 web nodes including caching and permission checking

I have a custom product type that gets displayed in a custom listing web part. I was trying to cache the items for performance reasons, but it's also important to check user permissions as not all products are visible to all users.
private static IList<TreeNode> GetUniqueProducts(string clientId, string path)
{
var pages = CacheHelper.Cache(cs => GetProducts(cs, path), new CacheSettings(10, "cus|" + clientId));
return GetUniqueProductNamesItems(pages);
}
private static IList<TreeNode> GetProducts(CacheSettings cacheSettings, string rootPath)
{
var pages = DocumentHelper.GetDocuments().Types("CUS.Product")
.Path(rootPath, PathTypeEnum.Children)
.Published().CheckPermissions().ToList();
if (cacheSettings.Cached)
{
cacheSettings.CacheDependency = CacheHelper.GetCacheDependency("nodes|custom|cus.product|all");
}
return pages;
}
However I realise that this is caching the first user's list of products. When in fact I want to store the full list of products in cache - but then check permissions before they get displayed.
The only way of checking permission seems to be as part of a DocumentQuery as per above - but I don't know how to apply that to a cached list of products - or on an individual node.
So is there a good way to achieve what I want? Without having to loop through each node and individually check user is authorised to access the node ?
You are missing the caching part in your code and I am not quire sure about your cache dependencies.
var pages = CacheHelper.Cache(cs =>
{
var result = CMS.DocumentEngine.DocumentHelper.GetDocuments().Types("CUS.Product").Path(rooPath, CMS.DocumentEngine.PathTypeEnum.Children).Published().ToList();
if (cs.Cached) { cs.CacheDependency = CacheHelper.GetCacheDependency("cus.product|all"); }
return result;
},
new CacheSettings(CacheHelper.CacheMinutes(CurrentSite.SiteName), "custom_products"));
If you checking the user read permissions it means you kinda caching per user. Then your cache should be done per user i.e. cachename should be "custom_products"+ UserID.ToString() or something like this.

Bringing Active Directory Users using JNDI in multiple threads

I have designed an application which brings the users from the active directory to an MySQL database, and shows them on GUI. It also brings the groups of which a user is a member of.
So, my program works this way:
for(String domain : allConfiguredADomains) {
LdapContext domainCtx = getDomainCtx(domain);
// Bring all users from this domain and store them in DB
getAllUsersForDomain(domain, domainCtx);
// Bring all the groups for every user
getAllGroupsForUsersInTheDomain(domain, domainCtx)
}
void getAllUsersForDomain(String domain, LdapContext domainCtx) {
String filter = "(objectClass=User)"
NamingEnumeration<SearchResult> result = domainCtx.search(domain, filter, ..);
while(result.hasMoreElements()) {
SearchResult searchResult = (SearchResult) result.nextElement();
// Process and store in database
storeUserInDatabase(searchResult);
}
}
void getAllGroupsForUsersInTheDomain(String domain, LdapContext domainCtx) {
List<String> userDistinguishedNames = getAllUsersFromDatabase("distinguishedName");
for(String userDn : userDistinguishedNames) {
String filter = "(&(objectClass=Group)(distinguishedName=" + userDn + "))";
NamingEnumeration<SearchResult> result = domainCtx.search(domain, filter, ..);
List<String> allGroupsOfUser = new List<String>();
while(result.hasMoreElements()) {
SearchResult searchResult = (SearchResult) result.nextElement();
String groupDistinguishedName = searchResult.getAttributes().get("distinguishedName").get();
allGroupsOfUser.add(groupDistinguishedName);
}
// Store them in database
storeAllGroupsOfUserInDatabase(userDn, allGroupsOfUser);
}
}
This application, however, takes lot of time, when there are too many users in the active directory. So, I decided to implement parallelism (using Threading). I divided this using search filter on distinguishedName of a user.
String filter = "(&(objectClass=User)(distinguishedName=a*"))";
and so on.. in each thread while fetching users.
I got better performance, but still not so good. Can someone suggest
a better way ?
Also, I don't have an idea how can I introduce
parallelism while fetching groups ?
If someone has any suggestions to do this better with powershell or C#, please suggest, I am open to technology.
Please note: reading user attribute memberOf does not provide all groups, hence I am fetching groups separately.
I'm not an Active Directory expert - just wanted to share some thoughts.
Threading by alphabet letter allows a maximum of 26 threads. Have you considered creating search threads by some other attributes, group membership etc? This might let you create more threads.
Review the Active Directory docs to see whether there is a way to improve search performance (for example, with a database we could create an index).

Processing an emaillist async in MVC4

I'm trying to make my MVC4-website check to see if people should be alerted with an email because they haven't done something.
I'm having a hard time figuring out how to approach this. I checked if the shared hosting platform would allow me to activate some sort of cronjob, but this is not available.
So now my idea is to perform this check on each page-request, which already seems suboptimal (because of the overhead). But I thought that with using an async it would not be in the way of people just visiting the site.
I first tried to do this in the Application_BeginRequest method in Global.asax, but then it gets called multiple times per page-request, so that didn't work.
Next I found that I can make a Global Filter which executes on OnResultExecuted, which would seemed promising, but still it's no go.
The problem I get there is that I'm using MVCMailer to send the mails, and when I execute it I get the error: {"Value cannot be null.\r\nParameter name: httpContext"}
This probably means that mailer needs the context.
The code I now have in my global filter is the following:
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
base.OnResultExecuted(filterContext);
HandleEmptyProfileAlerts();
}
private void HandleEmptyProfileAlerts()
{
new Thread(() =>
{
bool active = false;
new UserMailer().AlertFirst("bla#bla.com").Send();
DB db = new DB();
DateTime CutoffDate = DateTime.Now.AddDays(-5);
var ProfilesToAlert = db.UserProfiles.Where(x => x.CreatedOn < CutoffDate && !x.ProfileActive && x.AlertsSent.Where(y => y.AlertType == "First").Count() == 0).ToList();
foreach (UserProfile up in ProfilesToAlert)
{
if (active)
{
new UserMailer().AlertFirst(up.UserName).Send();
up.AlertsSent.Add(new UserAlert { AlertType = "First", DateSent = DateTime.Now, UserProfileID = up.UserId });
}
else
System.Diagnostics.Debug.WriteLine(up.UserName);
}
db.SaveChanges();
}).Start();
}
So my question is, am I going about this the right way, and if so, how can I make sure that MVCMailer gets the right context?
The usual way to do this kind of thing is to have a single background thread that periodically does the checks you're interested in.
You would start the thread from Application_Start(). It's common to use a database to queue and store work items, although it can also be done in memory if it's better for your app.

Control Report Permission Based on Parameters in Reporting Services

Assume we have a report called SalesSummary for a large department. This department has many smaller teams for each product. People should be able to see information about their own product, not other teams' products. We also have one domain group for each of these teams.
Copying SalesSummary report for each team and setting the permission is not the best option since we have many products. I was thinking to use a code similar to below on RS, but it doesn't work. Apparently, System.Security.Principal.WindowsPrincipal is disabled by default on RS.
Public Function isPermitted() As Boolean
Dim Principal As New System.Security.Principal.WindowsPrincipal(System.Security.Principal.WindowsIdentity.GetCurrent())
If (Principal.IsInRole("group_prod")) Then
Return true
Else
Return false
End If
End Function
I also thought I can send the userID from RS to SQL server, and inside my SP I can use a code similar to below to query active directory. This also doesn't work due to security restriction.
SELECT
*
FROM OPENQUERY(ADSI,'SELECT cn, ADsPath FROM ''LDAP://DC=Fabricam,DC=com'' WHERE objectCategory=''group''')
Is there any easier way to achieve this goal?
Thanks for the help!
The first option you suggested (using embedded code to identify the executing user) will not be reliable. SSRS code is not necessarily executed as the user accessing the report, and may not have access to that users credentials, such as when running a subscription.
Your second approach will work, but requires the appropriate permissions for your SQL server service account to query Active Directory.
Another approach is to maintain a copy of the group membership or user permissions in a SQL table. This table can be updated by hand or with an automated process. Then you can easily incorporate this into both available parameters and core data queries.
So I ended up with this code:
PrincipalContext domain = new PrincipalContext(ContextType.Domain, "AD");
UserPrincipal user = UserPrincipal.FindByIdentity(domain, identityName);
//// if found - grab its groups
if (user != null)
{
PrincipalSearchResult<Principal> _groups = null;
int tries = 0;
//We have this while because GetGroups sometimes fails! Specially if you don't
// mention the domain in PrincipalContext
while (true)
{
try
{
_groups = user.GetGroups();
break;
}
catch (Exception ex)
{
logger.Debug("get groups failed", ex);
if (tries > 5) throw;
tries++;
}
}
// iterate over all groups, just gets groups related to this app
foreach (Principal p in _groups)
{
// make sure to add only group principals
if (p is GroupPrincipal)
{
if (p.Name.StartsWith(GROUP_IDENTIFIER))
{
this.groups.Add((GroupPrincipal)p);
this.groupNames.Add(p.Name);
}
}
}
}
Now, that you have a list of related group you can check the list to authorize the user!

why my sharepoint workflow always stop when i use this code?

I need to find an user in a list to set the assignedto task property, these informations are in a list. So i use this method :
public static SPUser GetSPUser(SPListItem item, string key){
SPFieldUser field = item.Fields[key] as SPFieldUser;
if (field != null)
{
SPFieldUserValue fieldValue = field.GetFieldValue(item[key].ToString()) as SPFieldUserValue;
if (fieldValue != null)
{
return fieldValue.User;
}
}
return null;
}
The problem is that when i use this method or this part of code, my workflow stop without saying anything. Here an exemple of code when i use it :
using (SPSite site = new SPSite(adress_of_my_site))
{
using (SPWeb web = site.OpenWeb())
{
SPList list = web.Lists["Acteurs du projet"];
SPView view = cobj_ListeDesActeursDuProjet.DefaultView;
SPListItemCollection itemcollection = list.GetItems(view);
foreach (SPListItem item in itemcollection)
{
SPUser lobj_acteur = Utilities.GetSPUser(item,"acteur");
// Dictionary<string,class>
ActeursDuProjet[item["Rôle"].ToString()] =
new ActeursDuProjet()
{
Login = lobj_acteur.LoginName,
Email = lobj_acteur.Email
};
}
}
}
If i comment the content of my foreach my workflow continue as well...
If anybody have an idea it will be cool.
Regards,
Loïc
edit: problem in the code
Here are some debugging tips that might help:
ULS logs
Any exceptions should be reported here in some detail.
Enable debugging for all .NET code
This will cause the debugger to break whenever an exception occurs in SharePoint as well as your code. The downside is that the debugger will break on 'normal' exceptions that cause no side effects. So don't be misled!
To enable: Go to Debug, Exceptions and tick Common Language Runtime Exceptions. Also go to Tools, Options, Debugging and untick Enable Just My Code. Then attach to w3wp.exe.
Commenting code
You could also comment out all of your code. If the workflow step fails, you know there is a problem elsewhere. If the workflow step passes, then start uncommenting code until it fails - then you know where to look.
I tried commenting this above but it didn't format nicely so here it is.
It probably is fine, but this looks fishy to me:
// Dictionary<string,class>
ActeursDuProjet[item["Rôle"].ToString()] =
new ActeursDuProjet()
{
Login = lobj_acteur.LoginName,
Email = lobj_acteur.Email
};
I would think it would be something like:
// dictionary declared somewhere earlier
Dictionary<string,ActeursDuProjet> roles = new Dictionary<string,ActeursDuProjet>();
// inside the foreach
string role = item["Rôle"].ToString();
if (!roles.ContainsKey(role))
{
ActeursDuProjet foo = new ActeursDuProjet();
foo.Login = lobj_acteur.LoginName;
foo.Email = lobj_acteur.Email;
roles.Add(role, foo);
}

Resources